Merge branch 'MDL-58428-master' of git://github.com/sarjona/moodle

This commit is contained in:
Sara Arjona 2019-02-26 13:25:54 +01:00
commit 44d5c25469
221 changed files with 5288 additions and 5467 deletions

View File

@ -43,8 +43,11 @@
"default": "Default value"
}
}}
<div class="form-item clearfix" id="{{id}}">
<div class="form-label">
{{!
Setting.
}}
<div class="form-item row" id="{{id}}">
<div class="form-label col-sm-3 text-sm-right">
<label {{#labelfor}}for="{{labelfor}}"{{/labelfor}}>
{{{title}}}
{{#override}}
@ -54,16 +57,16 @@
<div class="form-warning">{{warning}}</div>
{{/warning}}
</label>
<span class="form-shortname">{{{name}}}</span>
<span class="form-shortname d-block small text-muted">{{{name}}}</span>
</div>
<div class="form-setting">
<div class="form-setting col-sm-9">
{{#error}}
<div><span class="error">{{error}}</span></div>
{{/error}}
{{{element}}}
{{#default}}
<div class="form-defaultinfo {{#forceltr}}text-ltr{{/forceltr}}">{{{default}}}</div>
<div class="form-defaultinfo text-muted {{#forceltr}}text-ltr{{/forceltr}}">{{{default}}}</div>
{{/default}}
<div class="form-description mt-3">{{{description}}}</div>
</div>
<div class="form-description">{{{description}}}</div>
</div>

View File

@ -35,13 +35,16 @@
"haspreviewconfig": false
}
}}
{{!
Setting configcolourpicker.
}}
<div class="form-colourpicker defaultsnext">
<div class="admin_colourpicker clearfix">
{{#icon}}
{{>core/pix_icon}}
{{/icon}}
</div>
<input type="text" name="{{name}}" id="{{id}}" value="{{value}}" size="12" class="text-ltr">
<input type="text" name="{{name}}" id="{{id}}" value="{{value}}" size="12" class="form-control text-ltr">
{{#haspreviewconfig}}
<input type="button" id="{{id}}_preview" value={{#quote}}{{#str}}preview{{/str}}{{/quote}} class="admin_colourpicker_preview">
{{/haspreviewconfig}}

View File

@ -33,13 +33,18 @@
"options": [ { "name": "Minutes", "value": "mins", "selected": true } ]
}
}}
{{!
Setting configduration.
}}
<div class="form-duration defaultsnext">
<input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="text-ltr">
<label class="accesshide" for="{{id}}u">{{#str}}durationunits, admin{{/str}}</label>
<select id="{{id}}u" name="{{name}}[u]">
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/options}}
</select>
<div class="form-inline">
<input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr">
<label class="sr-only" for="{{id}}u">{{#str}}durationunits, admin{{/str}}</label>
<select id="{{id}}u" name="{{name}}[u]" class="form-control">
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/options}}
</select>
</div>
</div>

View File

@ -38,15 +38,20 @@
"valid": false
}
}}
{{!
Setting configfile.
}}
<div class="form-file defaultsnext">
<input type="text" name="{{name}}" id="{{id}}" size="{{size}}" value="{{value}}" class="text-ltr" {{#readonly}}readonly{{/readonly}}>
{{#showvalidity}}
{{#valid}}
<span class="pathok">&#x2714;</span>
{{/valid}}
{{^valid}}
<span class="patherror">&#x2718;</span>
{{/valid}}
{{/showvalidity}}
<div class="form-inline">
<input type="text" name="{{name}}" id="{{id}}" size="{{size}}" value="{{value}}" class="form-control text-ltr" {{#readonly}}readonly{{/readonly}}>
{{#showvalidity}}
{{#valid}}
<span class="text-success">&#x2714;</span>
{{/valid}}
{{^valid}}
<span class="text-danger">&#x2718;</span>
{{/valid}}
{{/showvalidity}}
</div>
</div>

View File

@ -34,9 +34,12 @@
{ "name": "Option 2", "value": "V", "selected": true } ]
}
}}
{{!
Setting configmultiselect.
}}
<div class="form-select">
<input type="hidden" name="{{name}}[xxxxx]" value="1">
<select id="{{id}}" name="{{name}}[]" size="{{size}}" multiple>
<select id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/options}}

View File

@ -53,13 +53,16 @@
]
}
}}
{{!
Setting configmultiselect with optgroup support.
}}
<div class="form-select">
<input type="hidden" name="{{name}}[xxxxx]" value="1">
<select id="{{id}}" name="{{name}}[]" size="{{size}}" multiple>
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/options}}
{{#optgroups}}
<select id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/options}}
{{#optgroups}}
<optgroup label="{{label}}">
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>

View File

@ -34,8 +34,11 @@
]
}
}}
{{!
Setting configselect.
}}
<div class="form-select defaultsnext">
<select id="{{id}}" name="{{name}}">
<select id="{{id}}" name="{{name}}" class="custom-select">
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/options}}

View File

@ -51,8 +51,11 @@
]
}
}}
{{!
Setting configselect with optgroup support.
}}
<div class="form-select defaultsnext">
<select id="{{id}}" name="{{name}}">
<select id="{{id}}" name="{{name}}" class="custom-select">
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/options}}
@ -66,3 +69,4 @@
</select>
</div>

View File

@ -37,6 +37,9 @@
"attributes": [ { "name": "readonly", "value": "readonly" } ]
}
}}
{{!
Setting configtext.
}}
<div class="form-text defaultsnext">
<input type="text" name="{{name}}" value="{{value}}" size="{{size}}" id="{{id}}" class="{{#forceltr}}text-ltr{{/forceltr}}"{{#attributes}} {{name}}="{{value}}"{{/attributes}}>
<input type="text" name="{{name}}" value="{{value}}" size="{{size}}" id="{{id}}" class="form-control {{#forceltr}}text-ltr{{/forceltr}}">
</div>

View File

@ -36,6 +36,9 @@
"id": "test0"
}
}}
{{!
Setting configtextarea.
}}
<div class="form-textarea">
<textarea rows="{{rows}}" cols="{{cols}}" id="{{id}}" name="{{name}}" spellcheck="true" class="{{#forceltr}}text-ltr{{/forceltr}}">{{value}}</textarea>
<textarea rows="{{rows}}" cols="{{cols}}" id="{{id}}" name="{{name}}" spellcheck="true" class="form-control {{#forceltr}}text-ltr{{/forceltr}}">{{value}}</textarea>
</div>

View File

@ -39,18 +39,23 @@
]
}
}}
<div class="form-time defaultsnext text-ltr">
<label class="accesshide" for="{{id}}h">{{#str}}hours{{/str}}</label>
<select id="{{id}}h" name="{{name}}[h]">
{{#hours}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/hours}}
</select>:
<label class="accesshide" for="{{id}}m">{{#str}}minutes{{/str}}</label>
<select id="{{id}}m" name="{{name}}[m]">
{{#minutes}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/minutes}}
</select>
{{!
Setting configtime.
}}
<div class="form-time defaultsnext">
<div class="form-inline text-ltr">
<label class="sr-only" for="{{id}}h">{{#str}}hours{{/str}}</label>
<select id="{{id}}h" name="{{name}}[h]" class="custom-select">
{{#hours}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/hours}}
</select>:
<label class="sr-only" for="{{id}}m">{{#str}}minutes{{/str}}</label>
<select id="{{id}}m" name="{{name}}[m]" class="custom-select">
{{#minutes}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/minutes}}
</select>
</div>
</div>

View File

@ -37,9 +37,12 @@
]
}
}}
{{!
Setting courselist_frontpage.
}}
<div class="form-group">
{{#selects}}
<select id="{{id}}{{key}}" name="{{name}}[]" class="form-select">
<select id="{{id}}{{key}}" name="{{name}}[]" class="custom-select">
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/options}}

View File

@ -34,12 +34,13 @@
{{!
Setting description.
}}
<div class="form-item form-horizontal clearfix">
<div class="form-label">
<div class="form-item row">
<div class="form-label col-sm-3 text-sm-right">
<label>
{{{title}}}
</label>
<span class="form-shortname ">{{{name}}}</span>
</div>
<div class="controls felement fstatic">{{{description}}}</div>
</div>
<div class="form-setting col-sm-9">
<div class="form-description">{{{description}}}</div>
</div>
</div>

View File

@ -29,7 +29,10 @@
]
}
}}
<table class="generaltable">
{{!
Setting devicedetectregex.
}}
<table class="table table-striped">
<thead>
<tr>
<th>{{#str}}devicedetectregexexpression, admin{{/str}}</th>
@ -40,10 +43,10 @@
{{#expressions}}
<tr>
<td class="c{{index}}">
<input type="text" name="{{name}}[expression{{index}}]" class="form-text text-ltr" value="{{expression}}">
<input type="text" name="{{name}}[expression{{index}}]" class="form-control" value="{{expression}}">
</td>
<td class="c{{index}}">
<input type="text" name="{{name}}[value{{index}}]" class="form-text text-ltr" value="{{value}}">
<input type="text" name="{{name}}[value{{index}}]" class="form-control" value="{{value}}">
</td>
</tr>
{{/expressions}}

View File

@ -32,6 +32,9 @@
]
}
}}
{{!
Setting emoticons.
}}
<div class="form-group">
<table id="emoticonsetting" class="admintable generaltable">
<thead>
@ -48,7 +51,7 @@
<tr>
{{#fields}}
<td class="c{{index}}">
<input type="text" name="{{name}}[{{field}}]" class="form-text text-ltr" value="{{value}}">
<input type="text" name="{{name}}[{{field}}]" class="form-text form-control text-ltr" value="{{value}}">
</td>
{{/fields}}
<td>

View File

@ -37,8 +37,11 @@
"advanced": true
}
}}
{{!
Setting configselect.
}}
<div class="form-group">
<select id="{{id}}" name="{{name}}[value]" class="form-select">
<select id="{{id}}" name="{{name}}[value]" class="form-select custom-select">
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/options}}

View File

@ -36,8 +36,11 @@
"showsave": true
}
}}
{{!
Settings.
}}
<form action="{{actionurl}}" method="post" id="adminsettings">
<div class="settingsform clearfix">
<div class="settingsform">
{{#params}}
<input type="hidden" name="{{name}}" value="{{value}}">
<input type="hidden" name="action" value="save-settings">
@ -49,8 +52,10 @@
{{/title}}
{{{settings}}}
{{#showsave}}
<div class="form-buttons">
<input type="submit" class="form-submit" value={{#quote}}{{#str}}savechanges, admin{{/str}}{{/quote}}>
<div class="row">
<div class="offset-sm-3 col-sm-3">
<button type="submit" class="btn btn-primary">{{#str}}savechanges, admin{{/str}}</button>
</div>
</div>
{{/showsave}}
</div>

View File

@ -58,8 +58,10 @@
</fieldset>
{{/results}}
{{#showsave}}
<div class="form-buttons">
<input type="submit" class="form-submit" value={{#quote}}{{#str}}savechanges, admin{{/str}}{{/quote}}>
<div class="row">
<div class="offset-sm-3 col-sm-3">
<button type="submit" class="btn btn-primary">{{#str}}savechanges, admin{{/str}}</button>
</div>
</div>
{{/showsave}}
{{/hasresults}}

View File

@ -55,16 +55,12 @@ class behat_admin extends behat_base {
foreach ($data as $label => $value) {
// 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::TIMEOUT * 1000, self::PAGE_READY_JS);
}
$this->execute('behat_navigation::i_select_from_flat_navigation_drawer', [get_string('administrationsite')]);
// Search by label.
$searchbox = $this->find_field(get_string('searchinsettings', 'admin'));
$searchbox = $this->find_field(get_string('query', 'admin'));
$searchbox->setValue($label);
$submitsearch = $this->find('css', 'form.adminsearchform input[type=submit]');
$submitsearch = $this->find('css', 'form input[type=submit][name=search]');
$submitsearch->press();
$this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
@ -78,21 +74,24 @@ class behat_admin extends behat_base {
// Single element settings.
try {
$fieldxpath = "//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]" .
"[@id=//label[contains(normalize-space(.), $label)]/@for or " .
"@id=//span[contains(normalize-space(.), $label)]/preceding-sibling::label[1]/@for]";
$fieldxpath = "//*[self::input | self::textarea | self::select]" .
"[not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]" .
"[@id=//label[contains(normalize-space(.), $label)]/@for or " .
"@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");
$formfieldtypenode = $this->find('xpath', $fieldxpath .
"/ancestor::div[contains(concat(' ', @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')]";
$fieldxpath = "//*[label[contains(., $label)]|span[contains(., $label)]]" .
"/ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' form-item ')]" .
"/descendant::div[contains(concat(' ', @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.
@ -101,6 +100,7 @@ class behat_admin extends behat_base {
// Getting the class which contains the field type.
$classes = explode(' ', $formfieldtypenode->getAttribute('class'));
$type = false;
foreach ($classes as $class) {
if (substr($class, 0, 5) == 'form-') {
$type = substr($class, 5);

View File

@ -24,7 +24,7 @@
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
/**
* Test context 1

View File

@ -24,7 +24,7 @@
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
/**
* Test context 2

View File

@ -24,7 +24,7 @@
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../../../../../lib/behat/behat_base.php');
/**
* Default Theme test context 1

View File

@ -24,7 +24,7 @@
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../behat_test_context_1.php');
require_once(__DIR__ . '/../../core/behat_test_context_1.php');
/**
* Theme test context 1
@ -33,6 +33,6 @@ require_once(__DIR__ . '/../behat_test_context_1.php');
* @copyright 2016 Rajesh Taneja <rajesh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_theme_nofeatures_behat_test_context_1 extends behat_test_context_1 {
class behat_theme_nofeatures_behat_test_context_2 extends behat_test_context_2 {
}

View File

@ -24,7 +24,7 @@
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../../../../../lib/behat/behat_base.php');
/**
* Theme test context 2
@ -33,6 +33,6 @@ require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
* @copyright 2016 Rajesh Taneja <rajesh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_theme_nofeatures_test_context_2 extends behat_base {
class behat_theme_nofeatures_test_context_1 extends behat_base {
}

View File

@ -24,7 +24,7 @@
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../behat_test_context_1.php');
require_once(__DIR__ . '/../../core/behat_test_context_1.php');
/**
* Theme test context 1

View File

@ -24,7 +24,7 @@
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../../../../../lib/behat/behat_base.php');
/**
* Theme test context 2

View File

@ -108,7 +108,6 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
array('nofeatures', __DIR__.'/fixtures/theme/nofeatures'),
array('defaulttheme', __DIR__.'/fixtures/theme/defaulttheme'),
);
// List of themes is const for test.
if ($notheme) {
$themelist = array('defaulttheme');
@ -116,6 +115,13 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
$themelist = array('withfeatures', 'nofeatures', 'defaulttheme');
}
$thememap = [];
foreach ($themelist as $themename) {
$mock = $this->getMockBuilder('theme_config');
$mock->disableOriginalConstructor();
$thememap[] = [$themename, $mock->getMock()];
}
$behatconfigutil->expects($this->any())
->method('get_list_of_themes')
->will($this->returnValue($themelist));
@ -125,6 +131,11 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
->method('get_theme_test_directory')
->will($this->returnValueMap($map));
// Theme directory for testing.
$behatconfigutil->expects($this->any())
->method('get_theme_config')
->will($this->returnValueMap($thememap));
$behatconfigutil->expects($this->any())
->method('get_default_theme')
->will($this->returnValue('defaulttheme'));
@ -138,7 +149,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
public function test_get_config_file_contents_with_single_run() {
$mockbuilder = $this->getMockBuilder('behat_config_util');
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
$behatconfigutil = $mockbuilder->getMock();
@ -177,7 +188,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
public function test_get_config_file_contents_with_single_run_no_theme() {
$mockbuilder = $this->getMockBuilder('behat_config_util');
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
$behatconfigutil = $mockbuilder->getMock();
@ -231,7 +242,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
public function test_get_config_file_contents_with_parallel_run() {
$mockbuilder = $this->getMockBuilder('behat_config_util');
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
$behatconfigutil = $mockbuilder->getMock();
@ -334,7 +345,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
public function test_get_config_file_contents_with_parallel_run_optimize_tags() {
$mockbuilder = $this->getMockBuilder('behat_config_util');
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
$behatconfigutil = $mockbuilder->getMock();
@ -479,7 +490,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
$mockbuilder = $this->getMockBuilder('behat_config_util');
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_blacklisted_tests_for_theme',
'get_default_theme'));
'get_default_theme', 'get_theme_config'));
$behatconfigutil = $mockbuilder->getMock();
@ -543,7 +554,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
$mockbuilder = $this->getMockBuilder('behat_config_util');
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_blacklisted_tests_for_theme',
'get_default_theme'));
'get_default_theme', 'get_theme_config'));
$behatconfigutil = $mockbuilder->getMock();
@ -621,7 +632,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
public function test_core_features_to_include_in_specified_theme() {
$mockbuilder = $this->getMockBuilder('behat_config_util');
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
$behatconfigutil = $mockbuilder->getMock();

View File

@ -1,5 +1,8 @@
This files describes API changes in the tool_behat code.
=== 3.7 ===
* Behat will now look for behat step definitions in the current
theme and any parents the theme may have.
=== 2.7 ===
* Constants behat_base::cap_allow, behat_base::cap_prevent and
behat_base::cap_prohibit have been removed in favour of the

View File

@ -20,7 +20,6 @@
Moodle progress bar template for tool_lp.
The purpose of this template is to render a progress bar with a brief description.
Inherits core/columns-1to2.
Classes required for JS:
* none
@ -41,17 +40,8 @@
}
}}
<div class="row-fluid rtl-compatible">
<div class="span4">
<div class="progresstext">
{{$progresstext}}{{progresstextvalue}}{{/progresstext}}
</div>
</div>
<div class="span8">
<div class="progress">
<div class="bar" style="width: {{$percentage}}{{percentagevalue}}{{/percentage}}%;" role="progressbar" aria-valuenow="{{$percentage}}{{percentagevalue}}{{/percentage}}" aria-valuemin="0" aria-valuemax="100">
{{$percentlabel}}{{percentlabelvalue}}{{/percentlabel}}
</div>
</div>
</div>
<div id="progress-{{uniqid}}">
{{$progresstext}}{{progresstextvalue}}{{/progresstext}}
</div>
<progress class="progress" aria-describedby="progress-{{uniqid}}"
value="{{$percentage}}{{percentagevalue}}{{/percentage}}" max="100"></progress>

View File

@ -7,7 +7,7 @@ Feature: Clear scheduled task fail delay
Background:
Given the scheduled task "\core\task\send_new_user_passwords_task" has a fail delay of "60" seconds
And I log in as "admin"
And I navigate to "Server > Scheduled tasks" in site administration
And I navigate to "Server > Tasks > Scheduled tasks" in site administration
Scenario: Clear fail delay
When I click on "Clear" "text" in the "Send new user passwords" "table_row"

View File

@ -6,7 +6,7 @@ Feature: Manage scheduled tasks
Background:
Given I log in as "admin"
And I navigate to "Server > Scheduled tasks" in site administration
And I navigate to "Server > Tasks > Scheduled tasks" in site administration
Scenario: Disable scheduled task
When I click on "Edit task schedule: Log table cleanup" "link" in the "Log table cleanup" "table_row"

View File

@ -45,18 +45,22 @@
}
}}
<div class="modal" data-role="flexitour-step">
<div data-role="arrow"></div>
<div class="modal-dialog" role="document" data-role="flexitour-step">
<div class="modal-content">
<div class="tooltip-arrow" data-role="arrow"></div>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true" data-role="end">&times;</button>
<h3 data-placeholder="title"></h3>
<h5 class="modal-title" data-placeholder="title"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-role="end">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" data-placeholder="body">
</div>
<div class="modal-body" data-placeholder="body"></div>
<div class="modal-footer">
<div class="btn-group">
<button href="#" class="btn" data-role="previous">{{# str }} previous, moodle {{/ str }}</button>
<button href="#" class="btn btn-primary" data-role="next">{{# str }} next, moodle {{/ str }}</button>
</div>
<button class="btn" data-role="end"> {{# str }} endtour, tool_usertours {{/ str }} </button>
<button type="button" class="btn btn-secondary" data-role="previous">{{# str }} previous, moodle {{/ str }}</button>
<button type="button" class="btn btn-primary" data-role="next">{{# str }} next, moodle {{/ str }}</button>
<button class="btn btn-secondary" data-role="end"> {{# str }} endtour, tool_usertours {{/ str }} </button>
</div>
</div>
</div>

View File

@ -6,7 +6,7 @@ Feature: Add a bookmarks to an admin pages
Background:
Given I log in as "admin"
And I navigate to "Server > Scheduled tasks" in site administration
And I navigate to "Server > Tasks > Scheduled tasks" in site administration
And I click on "Bookmark this page" "link" in the "Admin bookmarks" "block"
And I log out

View File

@ -1,15 +1,18 @@
<div class="searchform">
<form action="{{actionurl}}" style="display: inline;">
<fieldset class="invisiblefieldset">
<legend class="accesshide">{{#str}}search{{/str}}</legend>
<input type="hidden" name="id" value="{{courseid}}">
<label class="accesshide" for="searchform_search">{{#str}}search{{/str}}</label>
<input id="searchform_search" name="search" type="text" size="16">
<button id="searchform_button" type="submit" title={{#quote}}{{#str}}search{{/str}}{{/quote}}>{{#str}}go{{/str}}</button><br>
<a href="{{advancedsearchurl}}">{{#str}}advancedsearch, block_search_forums{{/str}}</a>
{{#helpicon}}
{{>core/help_icon}}
{{/helpicon}}
</fieldset>
<form action="{{actionurl}}" class="form-inline">
<input type="hidden" name="id" value="{{courseid}}">
<div class="input-group w-100">
<label class="sr-only" for="searchform_search">{{#str}}search{{/str}}</label>
<input id="searchform_search" name="search" type="text" class="form-control" size="10">
<div class="input-group-append">
<button class="btn btn-secondary" id="searchform_button" type="submit">{{#str}}go{{/str}}</button>
</div>
</div>
</form>
<div class="mt-3">
<a href="{{advancedsearchurl}}">{{#str}}advancedsearch, block_search_forums{{/str}}</a>
{{#helpicon}}
{{>core/help_icon}}
{{/helpicon}}
</div>
</div>

View File

@ -143,14 +143,12 @@ class block_settings_renderer extends plugin_renderer_base {
}
public function search_form(moodle_url $formtarget, $searchvalue) {
$content = html_writer::start_tag('form', array('class'=>'adminsearchform', 'method'=>'get', 'action'=>$formtarget, 'role' => 'search'));
$content .= html_writer::start_tag('div');
$content .= html_writer::tag('label', s(get_string('searchinsettings', 'admin')), array('for'=>'adminsearchquery', 'class'=>'accesshide'));
$content .= html_writer::empty_tag('input', array('id'=>'adminsearchquery', 'type'=>'text', 'name'=>'query', 'value'=>s($searchvalue)));
$content .= html_writer::empty_tag('input', array('type'=>'submit', 'value'=>s(get_string('search'))));
$content .= html_writer::end_tag('div');
$content .= html_writer::end_tag('form');
return $content;
$data = [
'action' => $formtarget->out(false),
'label' => get_string('searchinsettings', 'admin'),
'searchvalue' => $searchvalue
];
return $this->render_from_template('block_settings/search_form', $data);
}
}

View File

@ -46,15 +46,13 @@ class behat_blocks extends behat_base {
* @param string $blockname
*/
public function i_add_the_block($blockname) {
$this->execute('behat_forms::i_set_the_field_to',
array("bui_addblock", $this->escape($blockname))
);
$addblock = get_string('addblock');
$this->execute('behat_navigation::i_select_from_flat_navigation_drawer', $addblock);
// If we are running without javascript we need to submit the form.
if (!$this->running_javascript()) {
$this->execute('behat_general::i_click_on_in_the',
array(get_string('go'), "button", "#add_block", "css_element")
);
$this->execute('behat_general::i_click_on_in_the', [$blockname, 'link_exact', '#region-main', 'css_element']);
} else {
$this->execute('behat_general::i_click_on_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']);
}
}
@ -108,7 +106,7 @@ class behat_blocks extends behat_base {
}
$this->execute('behat_general::i_click_on_in_the',
array(get_string('actions'), "link", $this->escape($blockname), "block")
array("a[data-toggle='dropdown']", "css_element", $this->escape($blockname), "block")
);
}
@ -137,7 +135,17 @@ class behat_blocks extends behat_base {
* @param string $blockname
*/
public function the_add_block_selector_should_contain_block($blockname) {
$this->execute('behat_forms::the_select_box_should_contain', [get_string('addblock'), $blockname]);
$addblock = get_string('addblock');
$this->execute('behat_navigation::i_select_from_flat_navigation_drawer', $addblock);
$cancelstr = get_string('cancel');
if (!$this->running_javascript()) {
$this->execute('behat_general::should_exist_in_the', [$blockname, 'link_exact', '#region-main', 'css_element']);
$this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'link_exact', '#region-main', 'css_element']);
} else {
$this->execute('behat_general::should_exist_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']);
$this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'button', $addblock, 'dialogue']);
}
}
/**
@ -147,6 +155,16 @@ class behat_blocks extends behat_base {
* @param string $blockname
*/
public function the_add_block_selector_should_not_contain_block($blockname) {
$this->execute('behat_forms::the_select_box_should_not_contain', [get_string('addblock'), $blockname]);
$addblock = get_string('addblock');
$this->execute('behat_navigation::i_select_from_flat_navigation_drawer', $addblock);
$cancelstr = get_string('cancel');
if (!$this->running_javascript()) {
$this->execute('behat_general::should_not_exist_in_the', [$blockname, 'link_exact', '#region-main', 'css_element']);
$this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'link_exact', '#region-main', 'css_element']);
} else {
$this->execute('behat_general::should_not_exist_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']);
$this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'button', $addblock, 'dialogue']);
}
}
}

View File

@ -24,15 +24,41 @@
"day": "Today",
"url": "http://example.com/",
"title": "Monday 2nd January",
"content": "<img class='icon smallicon' alt='icon' src='../../../pix/i/siteevent.svg'>Test site event"
"content": "<img class='icon smallicon' src='../../../pix/i/siteevent.svg'>Test site event"
}
}}
{{< core/hover_tooltip }}
{{$anchor}}
<a href="{{url}}">{{$day}}{{day}}{{/day}}</a>
{{/anchor}}
{{$tooltip}}
<b>{{$title}}{{title}}{{/title}}</b>
{{$content}}{{{content}}}{{/content}}
{{/tooltip}}
{{/ core/hover_tooltip }}
<a {{!
}} id="calendar-day-popover-link-{{courseid}}-{{year}}-{{yday}}-{{uniqid}}"{{!
}} href="{{$url}}{{url}}{{/url}}"{{!
}} data-container="body"{{!
}} data-toggle="popover"{{!
}} data-html="true"{{!
}} data-trigger="hover"{{!
}} data-placement="top"{{!
}} data-title="{{$title}}{{title}}{{/title}}"{{!
}} data-alternate="{{$nocontent}}{{/nocontent}}"{{!
}}>{{$day}}{{day}}{{/day}}</a>
<div class="hidden">
{{$content}}{{/content}}
</div>
{{#js}}
require(['jquery'], function($) {
require(['theme_boost/popover'], function() {
var target = $("#calendar-day-popover-link-{{courseid}}-{{year}}-{{yday}}-{{uniqid}}");
target.popover({
content: function() {
var source = target.next().find("> *:not('.hidden')");
var content = $('<div>');
if (source.length) {
content.html(source.clone(false));
} else {
content.html(target.data('alternate'));
}
return content.html();
}
});
});
});
{{/js}}

View File

@ -139,13 +139,15 @@ class core_course_management_renderer extends plugin_renderer_base {
$listing = core_course_category::get(0)->get_children();
$attributes = array(
'class' => 'ml',
'role' => 'tree',
'aria-labelledby' => 'category-listing-title'
'class' => 'ml-1 list-unstyled',
'role' => 'tree',
'aria-labelledby' => 'category-listing-title'
);
$html = html_writer::start_div('category-listing');
$html .= html_writer::tag('h3', get_string('categories'), array('id' => 'category-listing-title'));
$html = html_writer::start_div('category-listing card w-100');
$html .= html_writer::tag('h3', get_string('categories'),
array('class' => 'card-header', 'id' => 'category-listing-title'));
$html .= html_writer::start_div('card-body');
$html .= $this->category_listing_actions($category);
$html .= html_writer::start_tag('ul', $attributes);
foreach ($listing as $listitem) {
@ -155,16 +157,17 @@ class core_course_management_renderer extends plugin_renderer_base {
$subcategories = $listitem->get_children();
}
$html .= $this->category_listitem(
$listitem,
$subcategories,
$listitem->get_children_count(),
$selectedcategory,
$selectedparents
$listitem,
$subcategories,
$listitem->get_children_count(),
$selectedcategory,
$selectedparents
);
}
$html .= html_writer::end_tag('ul');
$html .= $this->category_bulk_actions($category);
$html .= html_writer::end_div();
$html .= html_writer::end_div();
return $html;
}
@ -181,20 +184,20 @@ class core_course_management_renderer extends plugin_renderer_base {
* @return string
*/
public function category_listitem(core_course_category $category, array $subcategories, $totalsubcategories,
$selectedcategory = null, $selectedcategories = array()) {
$selectedcategory = null, $selectedcategories = array()) {
$isexpandable = ($totalsubcategories > 0);
$isexpanded = (!empty($subcategories));
$activecategory = ($selectedcategory === $category->id);
$attributes = array(
'class' => 'listitem listitem-category',
'data-id' => $category->id,
'data-expandable' => $isexpandable ? '1' : '0',
'data-expanded' => $isexpanded ? '1' : '0',
'data-selected' => $activecategory ? '1' : '0',
'data-visible' => $category->visible ? '1' : '0',
'role' => 'treeitem',
'aria-expanded' => $isexpanded ? 'true' : 'false'
'class' => 'listitem listitem-category list-group-item list-group-item-action',
'data-id' => $category->id,
'data-expandable' => $isexpandable ? '1' : '0',
'data-expanded' => $isexpanded ? '1' : '0',
'data-selected' => $activecategory ? '1' : '0',
'data-visible' => $category->visible ? '1' : '0',
'role' => 'treeitem',
'aria-expanded' => $isexpanded ? 'true' : 'false'
);
$text = $category->get_formatted_name();
if ($category->parent) {
@ -205,12 +208,12 @@ class core_course_management_renderer extends plugin_renderer_base {
}
$courseicon = $this->output->pix_icon('i/course', get_string('courses'));
$bcatinput = array(
'type' => 'checkbox',
'name' => 'bcat[]',
'value' => $category->id,
'class' => 'bulk-action-checkbox',
'aria-label' => get_string('bulkactionselect', 'moodle', $text),
'data-action' => 'select'
'type' => 'checkbox',
'name' => 'bcat[]',
'value' => $category->id,
'class' => 'bulk-action-checkbox',
'aria-label' => get_string('bulkactionselect', 'moodle', $text),
'data-action' => 'select'
);
if (!$category->can_resort_subcategories() && !$category->has_manage_capability()) {
@ -220,34 +223,36 @@ class core_course_management_renderer extends plugin_renderer_base {
$viewcaturl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
if ($isexpanded) {
$icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'), 'moodle', array('class' => 'tree-icon', 'title' => ''));
$icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'),
'moodle', array('class' => 'tree-icon', 'title' => ''));
$icon = html_writer::link(
$viewcaturl,
$icon,
array(
'class' => 'float-left',
'data-action' => 'collapse',
'title' => get_string('collapsecategory', 'moodle', $text),
'aria-controls' => 'subcategoryof'.$category->id
)
$viewcaturl,
$icon,
array(
'class' => 'float-left',
'data-action' => 'collapse',
'title' => get_string('collapsecategory', 'moodle', $text),
'aria-controls' => 'subcategoryof'.$category->id
)
);
} else if ($isexpandable) {
$icon = $this->output->pix_icon('t/switch_plus', get_string('expand'), 'moodle', array('class' => 'tree-icon', 'title' => ''));
$icon = $this->output->pix_icon('t/switch_plus', get_string('expand'),
'moodle', array('class' => 'tree-icon', 'title' => ''));
$icon = html_writer::link(
$viewcaturl,
$icon,
array(
'class' => 'float-left',
'data-action' => 'expand',
'title' => get_string('expandcategory', 'moodle', $text)
)
$viewcaturl,
$icon,
array(
'class' => 'float-left',
'data-action' => 'expand',
'title' => get_string('expandcategory', 'moodle', $text)
)
);
} else {
$icon = $this->output->pix_icon(
'i/empty',
'',
'moodle',
array('class' => 'tree-icon'));
'i/empty',
'',
'moodle',
array('class' => 'tree-icon'));
$icon = html_writer::span($icon, 'float-left');
}
$actions = \core_course\management\helper::get_category_listitem_actions($category);
@ -268,7 +273,7 @@ class core_course_management_renderer extends plugin_renderer_base {
$textattributes['aria-label'] = $textlabel;
}
$html .= html_writer::link($viewcaturl, $text, $textattributes);
$html .= html_writer::start_div('float-right');
$html .= html_writer::start_div('float-right d-flex');
if ($category->idnumber) {
$html .= html_writer::tag('span', s($category->idnumber), array('class' => 'dimmed idnumber'));
}
@ -277,28 +282,28 @@ class core_course_management_renderer extends plugin_renderer_base {
}
$countid = 'course-count-'.$category->id;
$html .= html_writer::span(
html_writer::span($category->get_courses_count()) .
html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid)) .
$courseicon,
'course-count dimmed',
array('aria-labelledby' => $countid)
html_writer::span($category->get_courses_count()) .
html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid)) .
$courseicon,
'course-count dimmed',
array('aria-labelledby' => $countid)
);
$html .= html_writer::end_div();
$html .= html_writer::end_div();
if ($isexpanded) {
$html .= html_writer::start_tag('ul',
array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
$catatlevel = \core_course\management\helper::get_expanded_categories($category->path);
$catatlevel[] = array_shift($selectedcategories);
$catatlevel = array_unique($catatlevel);
foreach ($subcategories as $listitem) {
$childcategories = (in_array($listitem->id, $catatlevel)) ? $listitem->get_children() : array();
$html .= $this->category_listitem(
$listitem,
$childcategories,
$listitem->get_children_count(),
$selectedcategory,
$selectedcategories
$listitem,
$childcategories,
$listitem->get_children_count(),
$selectedcategory,
$selectedcategories
);
}
$html .= html_writer::end_tag('ul');
@ -327,7 +332,7 @@ class core_course_management_renderer extends plugin_renderer_base {
if ($cancreatecategory) {
$url = new moodle_url('/course/editcategory.php', array('parent' => $category->id));
$actions[] = html_writer::link($url, get_string('createnewcategory'));
$actions[] = html_writer::link($url, get_string('createnewcategory'), array('class' => 'btn btn-default'));
}
if (core_course_category::can_approve_course_requests()) {
$actions[] = html_writer::link(new moodle_url('/course/pending.php'), get_string('coursespending'));
@ -335,7 +340,7 @@ class core_course_management_renderer extends plugin_renderer_base {
if (count($actions) === 0) {
return '';
}
return html_writer::div(join(' | ', $actions), 'listing-actions category-listing-actions');
return html_writer::div(join(' ', $actions), 'listing-actions category-listing-actions mb-3');
}
/**
@ -480,20 +485,19 @@ class core_course_management_renderer extends plugin_renderer_base {
* Renders a course listing.
*
* @param core_course_category $category The currently selected category. This is what the listing is focused on.
* @param core_course_list_element $course The currently selected course.
* @param core_course_list_element $course The currently selected course.
* @param int $page The page being displayed.
* @param int $perpage The number of courses to display per page.
* @param string|null $viewmode The view mode the page is in, one out of 'default', 'combined', 'courses' or 'categories'.
* @return string
*/
public function course_listing(core_course_category $category = null, core_course_list_element $course = null,
$page = 0, $perpage = 20,
$viewmode = 'default') {
$page = 0, $perpage = 20, $viewmode = 'default') {
if ($category === null) {
$html = html_writer::start_div('select-a-category');
$html .= html_writer::tag('h3', get_string('courses'),
array('id' => 'course-listing-title', 'tabindex' => '0'));
array('id' => 'course-listing-title', 'tabindex' => '0'));
$html .= $this->output->notification(get_string('selectacategory'), 'notifymessage');
$html .= html_writer::end_div();
return $html;
@ -507,8 +511,8 @@ class core_course_management_renderer extends plugin_renderer_base {
$page = $totalpages - 1;
}
$options = array(
'offset' => $page * $perpage,
'limit' => $perpage
'offset' => $page * $perpage,
'limit' => $perpage
);
$courseid = isset($course) ? $course->id : null;
$class = '';
@ -519,15 +523,16 @@ class core_course_management_renderer extends plugin_renderer_base {
$class .= ' lastpage';
}
$html = html_writer::start_div('course-listing'.$class, array(
'data-category' => $category->id,
'data-page' => $page,
'data-totalpages' => $totalpages,
'data-totalcourses' => $totalcourses,
'data-canmoveoutof' => $category->can_move_courses_out_of() && $category->can_move_courses_into()
$html = html_writer::start_div('card course-listing w-100'.$class, array(
'data-category' => $category->id,
'data-page' => $page,
'data-totalpages' => $totalpages,
'data-totalcourses' => $totalcourses,
'data-canmoveoutof' => $category->can_move_courses_out_of() && $category->can_move_courses_into()
));
$html .= html_writer::tag('h3', $category->get_formatted_name(),
array('id' => 'course-listing-title', 'tabindex' => '0'));
array('id' => 'course-listing-title', 'tabindex' => '0', 'class' => 'card-header'));
$html .= html_writer::start_div('card-body');
$html .= $this->course_listing_actions($category, $course, $perpage);
$html .= $this->listing_pagination($category, $page, $perpage, false, $viewmode);
$html .= html_writer::start_tag('ul', array('class' => 'ml course-list', 'role' => 'group'));
@ -538,6 +543,7 @@ class core_course_management_renderer extends plugin_renderer_base {
$html .= $this->listing_pagination($category, $page, $perpage, true, $viewmode);
$html .= $this->course_bulk_actions($category);
$html .= html_writer::end_div();
$html .= html_writer::end_div();
return $html;
}
@ -588,7 +594,7 @@ class core_course_management_renderer extends plugin_renderer_base {
* This function will be called for every course being displayed by course_listing.
*
* @param core_course_category $category The currently selected category and the category the course belongs to.
* @param core_course_list_element $course The course to produce HTML for.
* @param core_course_list_element $course The course to produce HTML for.
* @param int $selectedcourse The id of the currently selected course.
* @return string
*/
@ -596,19 +602,19 @@ class core_course_management_renderer extends plugin_renderer_base {
$text = $course->get_formatted_name();
$attributes = array(
'class' => 'listitem listitem-course',
'data-id' => $course->id,
'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
'data-visible' => $course->visible ? '1' : '0'
'class' => 'listitem listitem-course list-group-item list-group-item-action',
'data-id' => $course->id,
'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
'data-visible' => $course->visible ? '1' : '0'
);
$bulkcourseinput = array(
'type' => 'checkbox',
'name' => 'bc[]',
'value' => $course->id,
'class' => 'bulk-action-checkbox',
'aria-label' => get_string('bulkactionselect', 'moodle', $text),
'data-action' => 'select'
'type' => 'checkbox',
'name' => 'bc[]',
'value' => $course->id,
'class' => 'bulk-action-checkbox',
'aria-label' => get_string('bulkactionselect', 'moodle', $text),
'data-action' => 'select'
);
if (!$category->has_manage_capability()) {
// Very very hardcoded here.
@ -654,7 +660,7 @@ class core_course_management_renderer extends plugin_renderer_base {
$actions = array();
if ($category->can_create_course()) {
$url = new moodle_url('/course/edit.php', array('category' => $category->id, 'returnto' => 'catmanage'));
$actions[] = html_writer::link($url, get_string('createnewcourse'));
$actions[] = html_writer::link($url, get_string('createnewcourse'), array('class' => 'btn btn-default'));
}
if ($category->can_request_course()) {
// Request a new course.
@ -675,42 +681,42 @@ class core_course_management_renderer extends plugin_renderer_base {
$timecreatedurl = new moodle_url($baseurl, array('resort' => 'timecreated'));
$timecreateddescurl = new moodle_url($baseurl, array('resort' => 'timecreateddesc'));
$menu = new action_menu(array(
new action_menu_link_secondary($fullnameurl,
null,
get_string('sortbyx', 'moodle', get_string('fullnamecourse'))),
new action_menu_link_secondary($fullnameurldesc,
null,
get_string('sortbyxreverse', 'moodle', get_string('fullnamecourse'))),
new action_menu_link_secondary($shortnameurl,
null,
get_string('sortbyx', 'moodle', get_string('shortnamecourse'))),
new action_menu_link_secondary($shortnameurldesc,
null,
get_string('sortbyxreverse', 'moodle', get_string('shortnamecourse'))),
new action_menu_link_secondary($idnumberurl,
null,
get_string('sortbyx', 'moodle', get_string('idnumbercourse'))),
new action_menu_link_secondary($idnumberdescurl,
null,
get_string('sortbyxreverse', 'moodle', get_string('idnumbercourse'))),
new action_menu_link_secondary($timecreatedurl,
null,
get_string('sortbyx', 'moodle', get_string('timecreatedcourse'))),
new action_menu_link_secondary($timecreateddescurl,
null,
get_string('sortbyxreverse', 'moodle', get_string('timecreatedcourse')))
new action_menu_link_secondary($fullnameurl,
null,
get_string('sortbyx', 'moodle', get_string('fullnamecourse'))),
new action_menu_link_secondary($fullnameurldesc,
null,
get_string('sortbyxreverse', 'moodle', get_string('fullnamecourse'))),
new action_menu_link_secondary($shortnameurl,
null,
get_string('sortbyx', 'moodle', get_string('shortnamecourse'))),
new action_menu_link_secondary($shortnameurldesc,
null,
get_string('sortbyxreverse', 'moodle', get_string('shortnamecourse'))),
new action_menu_link_secondary($idnumberurl,
null,
get_string('sortbyx', 'moodle', get_string('idnumbercourse'))),
new action_menu_link_secondary($idnumberdescurl,
null,
get_string('sortbyxreverse', 'moodle', get_string('idnumbercourse'))),
new action_menu_link_secondary($timecreatedurl,
null,
get_string('sortbyx', 'moodle', get_string('timecreatedcourse'))),
new action_menu_link_secondary($timecreateddescurl,
null,
get_string('sortbyxreverse', 'moodle', get_string('timecreatedcourse')))
));
$menu->set_menu_trigger(get_string('resortcourses'));
$actions[] = $this->render($menu);
}
$strall = get_string('all');
$menu = new action_menu(array(
new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 5)), null, 5),
new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 10)), null, 10),
new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 20)), null, 20),
new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 50)), null, 50),
new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 100)), null, 100),
new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 999)), null, $strall),
new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 5)), null, 5),
new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 10)), null, 10),
new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 20)), null, 20),
new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 50)), null, 50),
new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 100)), null, 100),
new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 999)), null, $strall),
));
if ((int)$perpage === 999) {
$perpage = $strall;
@ -718,7 +724,7 @@ class core_course_management_renderer extends plugin_renderer_base {
$menu->attributes['class'] .= ' courses-per-page';
$menu->set_menu_trigger(get_string('perpagea', 'moodle', $perpage));
$actions[] = $this->render($menu);
return html_writer::div(join(' | ', $actions), 'listing-actions course-listing-actions');
return html_writer::div(join(' ', $actions), 'listing-actions course-listing-actions');
}
/**
@ -800,20 +806,25 @@ class core_course_management_renderer extends plugin_renderer_base {
/**
* Renderers detailed course information.
*
* @param core_course_list_element $course The course to display details for.
* @param core_course_list_element $course The course to display details for.
* @return string
*/
public function course_detail(core_course_list_element $course) {
$details = \core_course\management\helper::get_course_detail_array($course);
$fullname = $details['fullname']['value'];
$html = html_writer::start_div('course-detail');
$html .= html_writer::tag('h3', $fullname, array('id' => 'course-detail-title', 'tabindex' => '0'));
$html = html_writer::start_div('course-detail card');
$html .= html_writer::start_div('card-header');
$html .= html_writer::tag('h3', $fullname, array('id' => 'course-detail-title',
'class' => 'card-title', 'tabindex' => '0'));
$html .= html_writer::end_div();
$html .= html_writer::start_div('card-body');
$html .= $this->course_detail_actions($course);
foreach ($details as $class => $data) {
$html .= $this->detail_pair($data['key'], $data['value'], $class);
}
$html .= html_writer::end_div();
$html .= html_writer::end_div();
return $html;
}
@ -827,8 +838,8 @@ class core_course_management_renderer extends plugin_renderer_base {
*/
protected function detail_pair($key, $value, $class ='') {
$html = html_writer::start_div('detail-pair row yui3-g '.preg_replace('#[^a-zA-Z0-9_\-]#', '-', $class));
$html .= html_writer::div(html_writer::span($key), 'pair-key span3 col-md-3 yui3-u-1-4');
$html .= html_writer::div(html_writer::span($value), 'pair-value span9 col-md-9 m-b-1 yui3-u-3-4 form-inline');
$html .= html_writer::div(html_writer::span($key), 'pair-key col-md-3 yui3-u-1-4 font-weight-bold');
$html .= html_writer::div(html_writer::span($value), 'pair-value col-md-8 yui3-u-3-4');
$html .= html_writer::end_div();
return $html;
}
@ -836,7 +847,7 @@ class core_course_management_renderer extends plugin_renderer_base {
/**
* A collection of actions for a course.
*
* @param core_course_list_element $course The course to display actions for.
* @param core_course_list_element $course The course to display actions for.
* @return string
*/
public function course_detail_actions(core_course_list_element $course) {
@ -846,9 +857,10 @@ class core_course_management_renderer extends plugin_renderer_base {
}
$options = array();
foreach ($actions as $action) {
$options[] = $this->action_link($action['url'], $action['string']);
$options[] = $this->action_link($action['url'], $action['string'], null,
array('class' => 'btn btn-sm btn-secondary mr-1 mb-3'));
}
return html_writer::div(join(' | ', $options), 'listing-actions course-detail-listing-actions');
return html_writer::div(join('', $options), 'listing-actions course-detail-listing-actions');
}
/**
@ -893,7 +905,7 @@ class core_course_management_renderer extends plugin_renderer_base {
* @return string
*/
public function grid_start($id = null, $class = null) {
$gridclass = 'grid-row-r row-fluid';
$gridclass = 'grid-start grid-row-r d-flex flex-wrap row';
if (is_null($class)) {
$class = $gridclass;
} else {
@ -925,29 +937,14 @@ class core_course_management_renderer extends plugin_renderer_base {
*/
public function grid_column_start($size, $id = null, $class = null) {
// Calculate Bootstrap grid sizing.
$bootstrapclass = 'span'.$size.' col-md-'.$size;
if ($id == 'course-detail') {
$size = 12;
$bootstrapclass = 'col-md-'.$size;
} else {
$bootstrapclass = 'd-flex flex-wrap px-3 mb-3';
}
// Calculate YUI grid sizing.
if ($size === 12) {
$maxsize = 1;
$size = 1;
} else {
$maxsize = 12;
$divisors = array(8, 6, 5, 4, 3, 2);
foreach ($divisors as $divisor) {
if (($maxsize % $divisor === 0) && ($size % $divisor === 0)) {
$maxsize = $maxsize / $divisor;
$size = $size / $divisor;
break;
}
}
}
if ($maxsize > 1) {
$yuigridclass = "grid-col-{$size}-{$maxsize} grid-col";
} else {
$yuigridclass = "grid-col-1 grid-col";
}
$yuigridclass = "col-sm";
if (is_null($class)) {
$class = $yuigridclass . ' ' . $bootstrapclass;
@ -958,7 +955,7 @@ class core_course_management_renderer extends plugin_renderer_base {
if (!is_null($id)) {
$attributes['id'] = $id;
}
return html_writer::start_div($class, $attributes);
return html_writer::start_div($class . " grid_column_start", $attributes);
}
/**
@ -1058,14 +1055,14 @@ class core_course_management_renderer extends plugin_renderer_base {
*
* @param array $courses The courses to display.
* @param int $totalcourses The total number of courses to display.
* @param core_course_list_element $course The currently selected course if there is one.
* @param core_course_list_element $course The currently selected course if there is one.
* @param int $page The current page, starting at 0.
* @param int $perpage The number of courses to display per page.
* @param string $search The string we are searching for.
* @return string
*/
public function search_listing(array $courses, $totalcourses, core_course_list_element $course = null, $page = 0, $perpage = 20,
$search = '') {
$search = '') {
$page = max($page, 0);
$perpage = max($perpage, 2);
$totalpages = ceil($totalcourses / $perpage);
@ -1077,11 +1074,11 @@ class core_course_management_renderer extends plugin_renderer_base {
$last = false;
$i = $page * $perpage;
$html = html_writer::start_div('course-listing', array(
'data-category' => 'search',
'data-page' => $page,
'data-totalpages' => $totalpages,
'data-totalcourses' => $totalcourses
$html = html_writer::start_div('course-listing w-100', array(
'data-category' => 'search',
'data-page' => $page,
'data-totalpages' => $totalpages,
'data-totalcourses' => $totalcourses
));
$html .= html_writer::tag('h3', get_string('courses'));
$html .= $this->search_pagination($totalcourses, $page, $perpage);
@ -1172,7 +1169,7 @@ class core_course_management_renderer extends plugin_renderer_base {
*
* This function will be called for every course being displayed by course_listing.
*
* @param core_course_list_element $course The course to produce HTML for.
* @param core_course_list_element $course The course to produce HTML for.
* @param int $selectedcourse The id of the currently selected course.
* @return string
*/
@ -1180,20 +1177,20 @@ class core_course_management_renderer extends plugin_renderer_base {
$text = $course->get_formatted_name();
$attributes = array(
'class' => 'listitem listitem-course',
'data-id' => $course->id,
'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
'data-visible' => $course->visible ? '1' : '0'
'class' => 'listitem listitem-course list-group-item list-group-item-action',
'data-id' => $course->id,
'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
'data-visible' => $course->visible ? '1' : '0'
);
$bulkcourseinput = '';
if (core_course_category::get($course->category)->can_move_courses_out_of()) {
$bulkcourseinput = array(
'type' => 'checkbox',
'name' => 'bc[]',
'value' => $course->id,
'class' => 'bulk-action-checkbox',
'aria-label' => get_string('bulkactionselect', 'moodle', $text),
'data-action' => 'select'
'type' => 'checkbox',
'name' => 'bc[]',
'value' => $course->id,
'class' => 'bulk-action-checkbox',
'aria-label' => get_string('bulkactionselect', 'moodle', $text),
'data-action' => 'select'
);
}
$viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
@ -1302,16 +1299,26 @@ class core_course_management_renderer extends plugin_renderer_base {
$strsearchcourses = get_string("searchcourses");
$searchurl = new moodle_url('/course/management.php');
$output = html_writer::start_tag('form', array('id' => $formid, 'action' => $searchurl, 'method' => 'get',
'class' => 'form-inline'));
$output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset m-y-1'));
$output .= html_writer::tag('label', $strsearchcourses, array('for' => $inputid));
$output .= html_writer::empty_tag('input', array('type' => 'text', 'id' => $inputid, 'size' => $inputsize,
'name' => 'search', 'value' => s($value), 'class' => 'form-control m-x-1'));
$output .= html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('go'),
'class' => 'btn btn-secondary'));
$output = html_writer::start_div('row');
$output .= html_writer::start_div('col-md-12');
$output .= html_writer::start_tag('form', array('class' => 'card', 'id' => $formid,
'action' => $searchurl, 'method' => 'get'));
$output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset'));
$output .= html_writer::tag('div', $this->output->heading($strsearchcourses.': ', 2, 'm-0'),
array('class' => 'card-header'));
$output .= html_writer::start_div('card-body');
$output .= html_writer::start_div('input-group col-sm-6 col-lg-4 m-auto');
$output .= html_writer::empty_tag('input', array('class' => 'form-control', 'type' => 'text', 'id' => $inputid,
'size' => $inputsize, 'name' => 'search', 'value' => s($value)));
$output .= html_writer::start_tag('span', array('class' => 'input-group-btn'));
$output .= html_writer::tag('button', get_string('go'), array('class' => 'btn btn-primary', 'type' => 'submit'));
$output .= html_writer::end_tag('span');
$output .= html_writer::end_div();
$output .= html_writer::end_div();
$output .= html_writer::end_tag('fieldset');
$output .= html_writer::end_tag('form');
$output .= html_writer::end_div();
$output .= html_writer::end_div();
return $output;
}

View File

@ -344,13 +344,13 @@ class core_course_renderer extends plugin_renderer_base {
}
/**
* Renders html to display a course search form
* Renders html to display a course search form.
*
* @param string $value default value to populate the search field
* @param string $format display format - 'plain' (default), 'short' or 'navbar'
* @return string
*/
function course_search_form($value = '', $format = 'plain') {
public function course_search_form($value = '', $format = 'plain') {
static $count = 0;
$formid = 'coursesearch';
if ((++$count) > 1) {
@ -372,23 +372,19 @@ class core_course_renderer extends plugin_renderer_base {
$inputsize = 30;
}
$strsearchcourses= get_string("searchcourses");
$searchurl = new moodle_url('/course/search.php');
$output = html_writer::start_tag('form', array('id' => $formid, 'action' => $searchurl, 'method' => 'get'));
$output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset'));
$output .= html_writer::tag('label', $strsearchcourses.': ', array('for' => $inputid));
$output .= html_writer::empty_tag('input', array('type' => 'text', 'id' => $inputid,
'size' => $inputsize, 'name' => 'search', 'value' => s($value)));
$output .= html_writer::empty_tag('input', array('type' => 'submit',
'value' => get_string('go')));
$output .= html_writer::end_tag('fieldset');
$data = (object) [
'searchurl' => (new moodle_url('/course/search.php'))->out(false),
'id' => $formid,
'inputid' => $inputid,
'inputsize' => $inputsize,
'value' => $value
];
if ($format != 'navbar') {
$output .= $this->output->help_icon("coursesearch", "core");
$helpicon = new \help_icon('coursesearch', 'core');
$data->helpicon = $helpicon->export_for_template($this);
}
$output .= html_writer::end_tag('form');
return $output;
return $this->render_from_template('core_course/course_search_form', $data);
}
/**

View File

@ -186,10 +186,11 @@ class behat_course extends behat_base {
// We are on the frontpage.
if ($section) {
// Section 1 represents the contents on the frontpage.
$sectionxpath = "//body[@id='page-site-index']/descendant::div[contains(concat(' ',normalize-space(@class),' '),' sitetopic ')]";
$sectionxpath = "//body[@id='page-site-index']" .
"/descendant::div[contains(concat(' ',normalize-space(@class),' '),' sitetopic ')]";
} else {
// Section 0 represents "Site main menu" block.
$sectionxpath = "//div[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]";
$sectionxpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]";
}
} else {
// We are inside the course.
@ -201,15 +202,16 @@ class behat_course extends behat_base {
if ($this->running_javascript()) {
// Clicks add activity or resource section link.
$sectionxpath = $sectionxpath . "/descendant::div[@class='section-modchooser']/span/a";
$sectionxpath = $sectionxpath . "/descendant::div" .
"[contains(concat(' ', normalize-space(@class) , ' '), ' section-modchooser ')]/span/a";
$sectionnode = $this->find('xpath', $sectionxpath);
$sectionnode->click();
// Clicks the selected activity if it exists.
$activityxpath = "//div[@id='chooseform']/descendant::label" .
"/descendant::span[contains(concat(' ', normalize-space(@class), ' '), ' typename ')]" .
"[normalize-space(.)=$activityliteral]" .
"/parent::label/child::input";
"/descendant::span[contains(concat(' ', normalize-space(@class), ' '), ' typename ')]" .
"[normalize-space(.)=$activityliteral]" .
"/parent::label/child::input";
$activitynode = $this->find('xpath', $activityxpath);
$activitynode->doubleClick();
@ -217,8 +219,9 @@ class behat_course extends behat_base {
// Without Javascript.
// Selecting the option from the select box which contains the option.
$selectxpath = $sectionxpath . "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' section_add_menus ')]" .
"/descendant::select[option[normalize-space(.)=$activityliteral]]";
$selectxpath = $sectionxpath . "/descendant::div" .
"[contains(concat(' ', normalize-space(@class), ' '), ' section_add_menus ')]" .
"/descendant::select[option[normalize-space(.)=$activityliteral]]";
$selectnode = $this->find('xpath', $selectxpath);
$selectnode->selectOption($activity);
@ -230,7 +233,6 @@ class behat_course extends behat_base {
}
/**
* Opens a section edit menu if it is not already opened.
*
@ -248,7 +250,7 @@ class behat_course extends behat_base {
// If it is already opened we do nothing.
$xpath = $this->section_exists($sectionnumber);
$xpath .= "/descendant::div[contains(@class, 'section-actions')]/descendant::a[contains(@class, 'textmenu')]";
$xpath .= "/descendant::div[contains(@class, 'section-actions')]/descendant::a[contains(@data-toggle, 'dropdown')]";
$exception = new ExpectationException('Section "' . $sectionnumber . '" was not found', $this->getSession());
$menu = $this->find('xpath', $xpath, $exception);
@ -550,8 +552,8 @@ class behat_course extends behat_base {
// Edit menu should be visible.
if ($this->is_course_editor()) {
$xpath = $sectionxpath .
"/descendant::div[contains(@class, 'section-actions')]" .
"/descendant::a[contains(@class, 'textmenu')]";
"/descendant::div[contains(@class, 'section-actions')]" .
"/descendant::a[contains(@data-toggle, 'dropdown')]";
if (!$this->getSession()->getPage()->find('xpath', $xpath)) {
throw new ExpectationException('The section edit menu is not available', $this->getSession());
}
@ -843,15 +845,23 @@ class behat_course extends behat_base {
// If it is already opened we do nothing.
$activitynode = $this->get_activity_node($activityname);
$classes = array_flip(explode(' ', $activitynode->getAttribute('class')));
if (!empty($classes['action-menu-shown'])) {
// Find the menu.
$menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
if (!$menunode) {
throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
$this->getSession());
}
$expanded = $menunode->getAttribute('aria-expanded');
if ($expanded == 'true') {
return;
}
$this->execute('behat_course::i_click_on_in_the_activity',
array("a[role='menuitem']", "css_element", $this->escape($activityname))
array("a[data-toggle='dropdown']", "css_element", $this->escape($activityname))
);
$this->actions_menu_should_be_open($activityname);
}
/**
@ -869,13 +879,19 @@ class behat_course extends behat_base {
// If it is already closed we do nothing.
$activitynode = $this->get_activity_node($activityname);
$classes = array_flip(explode(' ', $activitynode->getAttribute('class')));
if (empty($classes['action-menu-shown'])) {
// Find the menu.
$menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
if (!$menunode) {
throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
$this->getSession());
}
$expanded = $menunode->getAttribute('aria-expanded');
if ($expanded != 'true') {
return;
}
$this->execute('behat_course::i_click_on_in_the_activity',
array("a[role='menuitem']", "css_element", $this->escape($activityname))
array("a[data-toggle='dropdown']", "css_element", $this->escape($activityname))
);
}
@ -892,10 +908,15 @@ class behat_course extends behat_base {
throw new DriverException('Activities actions menu not available when Javascript is disabled');
}
// If it is already closed we do nothing.
$activitynode = $this->get_activity_node($activityname);
$classes = array_flip(explode(' ', $activitynode->getAttribute('class')));
if (empty($classes['action-menu-shown'])) {
// Find the menu.
$menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
if (!$menunode) {
throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
$this->getSession());
}
$expanded = $menunode->getAttribute('aria-expanded');
if ($expanded != 'true') {
throw new ExpectationException(sprintf("The action menu for '%s' is not open", $activityname), $this->getSession());
}
}
@ -1039,18 +1060,18 @@ class behat_course extends behat_base {
// Determine the future new activity xpath from the former one.
$duplicatedxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" .
"[contains(., $activityliteral)]/following-sibling::li";
$duplicatedactionsmenuxpath = $duplicatedxpath . "/descendant::a[@role='menuitem']";
"[contains(., $activityliteral)]/following-sibling::li";
$duplicatedactionsmenuxpath = $duplicatedxpath . "/descendant::a[@data-toggle='dropdown']";
if ($this->running_javascript()) {
// We wait until the AJAX request finishes and the section is visible again.
$hiddenlightboxxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" .
"[contains(., $activityliteral)]" .
"/ancestor::li[contains(concat(' ', normalize-space(@class), ' '), ' section ')]" .
"/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]";
"[contains(., $activityliteral)]" .
"/ancestor::li[contains(concat(' ', normalize-space(@class), ' '), ' section ')]" .
"/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]";
$this->execute("behat_general::wait_until_exists",
array($this->escape($hiddenlightboxxpath), "xpath_element")
array($this->escape($hiddenlightboxxpath), "xpath_element")
);
// Close the original activity actions menu.
@ -1059,13 +1080,13 @@ class behat_course extends behat_base {
// The next sibling of the former activity will be the duplicated one, so we click on it from it's xpath as, at
// this point, it don't even exists in the DOM (the steps are executed when we return them).
$this->execute('behat_general::i_click_on',
array($this->escape($duplicatedactionsmenuxpath), "xpath_element")
array($this->escape($duplicatedactionsmenuxpath), "xpath_element")
);
}
// We force the xpath as otherwise mink tries to interact with the former one.
$this->execute('behat_general::i_click_on_in_the',
array(get_string('editsettings'), "link", $this->escape($duplicatedxpath), "xpath_element")
array(get_string('editsettings'), "link", $this->escape($duplicatedxpath), "xpath_element")
);
$this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
@ -1281,8 +1302,8 @@ class behat_course extends behat_base {
protected function is_course_editor() {
// We don't need to behat_base::spin() here as all is already loaded.
if (!$this->getSession()->getPage()->findButton(get_string('turneditingoff')) &&
!$this->getSession()->getPage()->findButton(get_string('turneditingon'))) {
if (!$this->getSession()->getPage()->findLink(get_string('turneditingoff')) &&
!$this->getSession()->getPage()->findLink(get_string('turneditingon'))) {
return false;
}
@ -1842,7 +1863,8 @@ class behat_course extends behat_base {
* @throws Behat\Mink\Exception\ExpectationException
*/
protected function user_clicks_on_management_listing_action($listingtype, $listingnode, $action) {
$actionsnode = $listingnode->find('xpath', "//*[contains(concat(' ', normalize-space(@class), ' '), '{$listingtype}-item-actions')]");
$actionsnode = $listingnode->find('xpath', "//*" .
"[contains(concat(' ', normalize-space(@class), ' '), '{$listingtype}-item-actions')]");
if (!$actionsnode) {
throw new ExpectationException("Could not find the actions for $listingtype", $this->getSession());
}
@ -1851,7 +1873,7 @@ class behat_course extends behat_base {
throw new ExpectationException("Expected action was not available or not found ($action)", $this->getSession());
}
if ($this->running_javascript() && !$actionnode->isVisible()) {
$actionsnode->find('css', 'a.toggle-display')->click();
$actionsnode->find('css', 'a[data-toggle=dropdown]')->click();
$actionnode = $actionsnode->find('css', '.action-'.$action);
}
$actionnode->click();
@ -1874,10 +1896,7 @@ class behat_course extends behat_base {
* @Given /^I navigate to course participants$/
*/
public function i_navigate_to_course_participants() {
$coursestr = behat_context_helper::escape(get_string('courses'));
$mycoursestr = behat_context_helper::escape(get_string('mycourses'));
$xpath = "//div[contains(@class,'block')]//li[p/*[string(.)=$coursestr or string(.)=$mycoursestr]]";
$this->execute('behat_general::i_click_on_in_the', [get_string('participants'), 'link', $xpath, 'xpath_element']);
$this->execute('behat_navigation::i_select_from_flat_navigation_drawer', get_string('participants'));
}
/**

View File

@ -602,8 +602,8 @@ class enrol_self_testcase extends advanced_testcase {
$selfplugin->enrol_user($instance1, $user2->id, $editingteacherrole->id);
$this->setUser($guest);
$noaccesshtml = get_string('noguestaccess', 'enrol') . $OUTPUT->continue_button(get_login_url());
$this->assertSame($noaccesshtml, $selfplugin->can_self_enrol($instance1, true));
$this->assertContains(get_string('noguestaccess', 'enrol'),
$selfplugin->can_self_enrol($instance1, true));
$this->setUser($user1);
$this->assertTrue($selfplugin->can_self_enrol($instance1, true));

View File

@ -134,124 +134,16 @@ class core_files_renderer extends plugin_renderer_base {
/**
* Returns html for displaying one file manager
*
* The main element in HTML must have id="filemanager-{$client_id}" and
* class="filemanager fm-loading";
* After all necessary code on the page (both html and javascript) is loaded,
* the class fm-loading will be removed and added class fm-loaded;
* The main element (class=filemanager) will be assigned the following classes:
* 'fm-maxfiles' - when filemanager has maximum allowed number of files;
* 'fm-nofiles' - when filemanager has no files at all (although there might be folders);
* 'fm-noitems' - when current view (folder) has no items - neither files nor folders;
* 'fm-updating' - when current view is being updated (usually means that loading icon is to be displayed);
* 'fm-nomkdir' - when 'Make folder' action is unavailable (empty($fm->options->subdirs) == true)
*
* Element with class 'filemanager-container' will be holding evens for dnd upload (dragover, etc.).
* It will have class:
* 'dndupload-ready' - when a file is being dragged over the browser
* 'dndupload-over' - when file is being dragged over this filepicker (additional to 'dndupload-ready')
* 'dndupload-uploading' - during the upload process (note that after dnd upload process is
* over, the file manager will refresh the files list and therefore will have for a while class
* fm-updating. Both waiting processes should look similar so the images don't jump for user)
*
* If browser supports Drag-and-drop, the body element will have class 'dndsupported',
* otherwise - 'dndnotsupported';
*
* Element with class 'fp-content' will be populated with files list;
* Element with class 'fp-btn-add' will hold onclick event for adding a file (opening filepicker);
* Element with class 'fp-btn-mkdir' will hold onclick event for adding new folder;
* Element with class 'fp-btn-download' will hold onclick event for download action;
*
* Element with class 'fp-path-folder' is a template for one folder in path toolbar.
* It will hold mouse click event and will be assigned classes first/last/even/odd respectfully.
* Parent element will receive class 'empty' when there are no folders to be displayed;
* The content of subelement with class 'fp-path-folder-name' will be substituted with folder name;
*
* Element with class 'fp-viewbar' will have the class 'enabled' or 'disabled' when view mode
* can be changed or not;
* Inside element with class 'fp-viewbar' there are expected elements with classes
* 'fp-vb-icons', 'fp-vb-tree' and 'fp-vb-details'. They will handle onclick events to switch
* between the view modes, the last clicked element will have the class 'checked';
*
* @param form_filemanager $fm
* @return string
*/
protected function fm_print_generallayout($fm) {
global $OUTPUT;
$options = $fm->options;
$client_id = $options->client_id;
$straddfile = get_string('addfile', 'repository');
$strmakedir = get_string('makeafolder', 'moodle');
$strdownload = get_string('downloadfolder', 'repository');
$strloading = get_string('loading', 'repository');
$strdroptoupload = get_string('droptoupload', 'moodle');
$icon_progress = $OUTPUT->pix_icon('i/loading_small', $strloading).'';
$restrictions = $this->fm_print_restrictions($fm);
$strdndnotsupported = get_string('dndnotsupported_insentence', 'moodle').$OUTPUT->help_icon('dndnotsupported');
$strdndenabledinbox = get_string('dndenabled_inbox', 'moodle');
$loading = get_string('loading', 'repository');
$straddfiletext = get_string('addfiletext', 'repository');
$strcreatefolder = get_string('createfolder', 'repository');
$strdownloadallfiles = get_string('downloadallfiles', 'repository');
$html = '
<div id="filemanager-'.$client_id.'" class="filemanager fm-loading">
<div class="fp-restrictions">
'.$restrictions.'
<span class="dnduploadnotsupported-message"> - '.$strdndnotsupported.' </span>
</div>
<div class="fp-navbar">
<div class="filemanager-toolbar">
<div class="fp-toolbar">
<div class="fp-btn-add">
<a role="button" title="' . $straddfile . '" href="#">
' . $this->pix_icon('a/add_file', $straddfiletext) . '
</a>
</div>
<div class="fp-btn-mkdir">
<a role="button" title="' . $strmakedir . '" href="#">
' . $this->pix_icon('a/create_folder', $strcreatefolder) . '
</a>
</div>
<div class="fp-btn-download">
<a role="button" title="' . $strdownload . '" href="#">
' . $this->pix_icon('a/download_all', $strdownloadallfiles) . '
</a>
</div>
<span class="fp-img-downloading">
' . $this->pix_icon('i/loading_small', '') . '
</span>
</div>
<div class="fp-viewbar">
<a title="'. get_string('displayicons', 'repository') .'" class="fp-vb-icons" href="#">
' . $this->pix_icon('fp/view_icon_active', get_string('displayasicons', 'repository'), 'theme') . '
</a>
<a title="'. get_string('displaydetails', 'repository') .'" class="fp-vb-details" href="#">
' . $this->pix_icon('fp/view_list_active', get_string('displayasdetails', 'repository'), 'theme') . '
</a>
<a title="'. get_string('displaytree', 'repository') .'" class="fp-vb-tree" href="#">
' . $this->pix_icon('fp/view_tree_active', get_string('displayastree', 'repository'), 'theme') . '
</a>
</div>
</div>
<div class="fp-pathbar">
<span class="fp-path-folder"><a class="fp-path-folder-name" href="#"></a></span>
</div>
</div>
<div class="filemanager-loading mdl-align">'.$icon_progress.'</div>
<div class="filemanager-container" >
<div class="fm-content-wrapper">
<div class="fp-content"></div>
<div class="fm-empty-container">
<div class="dndupload-message">'.$strdndenabledinbox.'<br/><div class="dndupload-arrow"></div></div>
</div>
<div class="dndupload-target">'.$strdroptoupload.'<br/><div class="dndupload-arrow"></div></div>
<div class="dndupload-progressbars"></div>
<div class="dndupload-uploadinprogress">'.$icon_progress.'</div>
</div>
<div class="filemanager-updating">'.$icon_progress.'</div>
</div>
</div>';
return $html;
$context = [
'client_id' => $fm->options->client_id,
'helpicon' => $this->help_icon('setmainfile', 'repository'),
'restrictions' => $this->fm_print_restrictions($fm)
];
return $this->render_from_template('core/filemanager_page_generallayout', $context);
}
/**
@ -350,140 +242,21 @@ class core_files_renderer extends plugin_renderer_base {
/**
* FileManager JS template for window with file information/actions.
*
* All content must be enclosed in one element, CSS for this class must define width and
* height of the window;
*
* Thumbnail image will be added as content to the element with class 'fp-thumbnail';
*
* Inside the window the elements with the following classnames must be present:
* 'fp-saveas', 'fp-author', 'fp-license', 'fp-path'. Inside each of them must be
* one input element (or select in case of fp-license and fp-path). They may also have labels.
* The elements will be assign with class 'uneditable' and input/select element will become
* disabled if they are not applicable for the particular file;
*
* There may be present elements with classes 'fp-original', 'fp-datemodified', 'fp-datecreated',
* 'fp-size', 'fp-dimensions', 'fp-reflist'. They will receive additional class 'fp-unknown' if
* information is unavailable. If there is information available, the content of embedded
* element with class 'fp-value' will be substituted with the value;
*
* The value of Original ('fp-original') is loaded in separate request. When it is applicable
* but not yet loaded the 'fp-original' element receives additional class 'fp-loading';
*
* The value of 'Aliases/Shortcuts' ('fp-reflist') is also loaded in separate request. When it
* is applicable but not yet loaded the 'fp-original' element receives additional class
* 'fp-loading'. The string explaining that XX references exist will replace content of element
* 'fp-refcount'. Inside '.fp-reflist .fp-value' each reference will be enclosed in <li>;
*
* Elements with classes 'fp-file-update', 'fp-file-download', 'fp-file-delete', 'fp-file-zip',
* 'fp-file-unzip', 'fp-file-setmain' and 'fp-file-cancel' will hold corresponding onclick
* events (there may be several elements with class 'fp-file-cancel');
*
* When confirm button is pressed and file is being selected, the top element receives
* additional class 'loading'. It is removed when response from server is received.
*
* When any of the input fields is changed, the top element receives class 'fp-changed';
* When current file can be set as main - top element receives class 'fp-cansetmain';
* When current file is folder/zip/file - top element receives respectfully class
* 'fp-folder'/'fp-zip'/'fp-file';
*
* @return string
*/
protected function fm_js_template_fileselectlayout() {
global $OUTPUT;
$strloading = get_string('loading', 'repository');
$iconprogress = $this->pix_icon('i/loading_small', $strloading).'';
$rv = '
<div class="filemanager fp-select">
<div class="fp-select-loading">
' . $this->pix_icon('i/loading_small', '') . '
</div>
<form class="form-horizontal">
<button class="fp-file-download">'.get_string('download').'</button>
<button class="fp-file-delete">'.get_string('delete').'</button>
<button class="fp-file-setmain">'.get_string('setmainfile', 'repository').'</button>
<span class="fp-file-setmain-help">'.$OUTPUT->help_icon('setmainfile', 'repository').'</span>
<button class="fp-file-zip">'.get_string('zip', 'editor').'</button>
<button class="fp-file-unzip">'.get_string('unzip').'</button>
<div class="fp-hr"></div>
<div class="fp-forminset">
<div class="fp-saveas control-group clearfix">
<label class="control-label">'.get_string('name', 'repository').'</label>
<div class="controls">
<input type="text"/>
</div>
</div>
<div class="fp-author control-group clearfix">
<label class="control-label">'.get_string('author', 'repository').'</label>
<div class="controls">
<input type="text"/>
</div>
</div>
<div class="fp-license control-group clearfix">
<label class="control-label">'.get_string('chooselicense', 'repository').'</label>
<div class="controls">
<select></select>
</div>
</div>
<div class="fp-path control-group clearfix">
<label class="control-label">'.get_string('path', 'repository').'</label>
<div class="controls">
<select></select>
</div>
</div>
<div class="fp-original control-group clearfix">
<label class="control-label">'.get_string('original', 'repository').'</label>
<div class="controls">
<span class="fp-originloading">'.$iconprogress.' '.$strloading.'</span><span class="fp-value"></span>
</div>
</div>
<div class="fp-reflist control-group clearfix">
<label class="control-label">'.get_string('referenceslist', 'repository').'</label>
<div class="controls">
<p class="fp-refcount"></p>
<span class="fp-reflistloading">'.$iconprogress.' '.$strloading.'</span>
<ul class="fp-value"></ul>
</div>
</div>
</div>
<div class="fp-select-buttons">
<button class="fp-file-update btn-primary btn">'.get_string('update', 'moodle').'</button>
<button class="fp-file-cancel btn-cancel btn">'.get_string('cancel').'</button>
</div>
</form>
<div class="fp-info clearfix">
<div class="fp-hr"></div>
<p class="fp-thumbnail"></p>
<div class="fp-fileinfo">
<div class="fp-datemodified">'.get_string('lastmodified', 'repository').' <span class="fp-value"></span></div>
<div class="fp-datecreated">'.get_string('datecreated', 'repository').' <span class="fp-value"></span></div>
<div class="fp-size">'.get_string('size', 'repository').' <span class="fp-value"></span></div>
<div class="fp-dimensions">'.get_string('dimensions', 'repository').' <span class="fp-value"></span></div>
</div>
</div>
</div>';
return $rv;
$context = [
'helpicon' => $this->help_icon('setmainfile', 'repository')
];
return $this->render_from_template('core/filemanager_fileselect', $context);
}
/**
* FileManager JS template for popup confirm dialogue window.
*
* Must have one top element, CSS for this element must define width and height of the window;
*
* content of element with class 'fp-dlg-text' will be replaced with dialog text;
* elements with classes 'fp-dlg-butconfirm' and 'fp-dlg-butcancel' will
* hold onclick events;
*
* @return string
*/
protected function fm_js_template_confirmdialog() {
$rv = '
<div class="filemanager fp-dlg">
<div class="fp-dlg-text"></div>
<button class="fp-dlg-butconfirm btn-primary btn">'.get_string('ok').'</button>
<button class="fp-dlg-butcancel btn-cancel btn">'.get_string('cancel').'</button>
</div>';
return $rv;
return $this->render_from_template('core/filemanager_confirmdialog', []);
}
/**
@ -529,112 +302,11 @@ class core_files_renderer extends plugin_renderer_base {
/**
* Template for FilePicker with general layout (not QuickUpload).
*
* Must have one top element containing everything else (recommended <div class="file-picker">),
* CSS for this element must define width and height of the filepicker window. Or CSS must
* define min-width, max-width, min-height and max-height and in this case the filepicker
* window will be resizeable;
*
* Element with class 'fp-viewbar' will have the class 'enabled' or 'disabled' when view mode
* can be changed or not;
* Inside element with class 'fp-viewbar' there are expected elements with classes
* 'fp-vb-icons', 'fp-vb-tree' and 'fp-vb-details'. They will handle onclick events to switch
* between the view modes, the last clicked element will have the class 'checked';
*
* Element with class 'fp-repo' is a template for displaying one repository. Other repositories
* will be attached as siblings (classes first/last/even/odd will be added respectfully).
* The currently selected repostory will have class 'active'. Contents of element with class
* 'fp-repo-name' will be replaced with repository name, source of image with class
* 'fp-repo-icon' will be replaced with repository icon;
*
* Element with class 'fp-content' is obligatory and will hold the current contents;
*
* Element with class 'fp-paging' will contain page navigation (will be deprecated soon);
*
* Element with class 'fp-path-folder' is a template for one folder in path toolbar.
* It will hold mouse click event and will be assigned classes first/last/even/odd respectfully.
* Parent element will receive class 'empty' when there are no folders to be displayed;
* The content of subelement with class 'fp-path-folder-name' will be substituted with folder name;
*
* Element with class 'fp-toolbar' will have class 'empty' if all 'Back', 'Search', 'Refresh',
* 'Logout', 'Manage' and 'Help' are unavailable for this repo;
*
* Inside fp-toolbar there are expected elements with classes fp-tb-back, fp-tb-search,
* fp-tb-refresh, fp-tb-logout, fp-tb-manage and fp-tb-help. Each of them will have
* class 'enabled' or 'disabled' if particular repository has this functionality.
* Element with class 'fp-tb-search' must contain empty form inside, it's contents will
* be substituted with the search form returned by repository (in the most cases it
* is generated with template core_repository_renderer::repository_default_searchform);
* Other elements must have either <a> or <button> element inside, it will hold onclick
* event for corresponding action; labels for fp-tb-back and fp-tb-logout may be
* replaced with those specified by repository;
*
* @return string
*/
protected function fp_js_template_generallayout() {
$rv = '
<div tabindex="0" class="file-picker fp-generallayout" role="dialog" aria-live="assertive">
<div class="fp-repo-area">
<ul class="fp-list" role="tablist">
<li class="fp-repo" role="tab" aria-selected="false" tabindex="-1">
<a href="#" tabindex="-1"><img class="fp-repo-icon" alt=" " width="16" height="16" />&nbsp;
<span class="fp-repo-name"></span>
</a>
</li>
</ul>
</div>
<div class="fp-repo-items" tabindex="0">
<div class="fp-navbar">
<div>
<div class="fp-toolbar">
<div class="fp-tb-back">
<a href="#">'.get_string('back', 'repository').'</a>
</div>
<div class="fp-tb-search">
<form></form>
</div>
<div class="fp-tb-refresh">
<a title="'. get_string('refresh', 'repository') .'" href="#">
' . $this->pix_icon('a/refresh', '') . '
</a>
</div>
<div class="fp-tb-logout">
<a title="'. get_string('logout', 'repository') .'" href="#">
' . $this->pix_icon('a/logout', '') . '
</a>
</div>
<div class="fp-tb-manage">
<a title="'. get_string('manageurl', 'repository') .'" href="#">
' . $this->pix_icon('a/setting', '') . '
</a>
</div>
<div class="fp-tb-help">
<a title="'. get_string('help', 'repository') .'" href="#">
' . $this->pix_icon('a/help', '') . '
</a>
</div>
<div class="fp-tb-message"></div>
</div>
<div class="fp-viewbar">
<a role="button" title="'. get_string('displayicons', 'repository') .'" class="fp-vb-icons" href="#">
' . $this->pix_icon('fp/view_icon_active', '', 'theme') . '
</a>
<a role="button" title="'. get_string('displaydetails', 'repository') .'" class="fp-vb-details" href="#">
' . $this->pix_icon('fp/view_list_active', '', 'theme') . '
</a>
<a role="button" title="'. get_string('displaytree', 'repository') .'" class="fp-vb-tree" href="#">
' . $this->pix_icon('fp/view_tree_active', '', 'theme') . '
</a>
</div>
<div class="fp-clear-left"></div>
</div>
<div class="fp-pathbar">
<span class="fp-path-folder"><a class="fp-path-folder-name" href="#"></a></span>
</div>
</div>
<div class="fp-content"></div>
</div>
</div>';
return $rv;
return $this->render_from_template('core/filemanager_modal_generallayout', []);
}
/**
@ -723,159 +395,19 @@ class core_files_renderer extends plugin_renderer_base {
/**
* FilePicker JS template for window appearing to select a file.
*
* All content must be enclosed in one element, CSS for this class must define width and
* height of the window;
*
* Thumbnail image will be added as content to the element with class 'fp-thumbnail';
*
* Inside the window the elements with the following classnames must be present:
* 'fp-saveas', 'fp-linktype-2', 'fp-linktype-1', 'fp-linktype-4', 'fp-setauthor',
* 'fp-setlicense'. Inside each of them must have one input element (or select in case of
* fp-setlicense). They may also have labels.
* The elements will be assign with class 'uneditable' and input/select element will become
* disabled if they are not applicable for the particular file;
*
* There may be present elements with classes 'fp-datemodified', 'fp-datecreated', 'fp-size',
* 'fp-license', 'fp-author', 'fp-dimensions'. They will receive additional class 'fp-unknown'
* if information is unavailable. If there is information available, the content of embedded
* element with class 'fp-value' will be substituted with the value;
*
* Elements with classes 'fp-select-confirm' and 'fp-select-cancel' will hold corresponding
* onclick events;
*
* When confirm button is pressed and file is being selected, the top element receives
* additional class 'loading'. It is removed when response from server is received.
*
* @return string
*/
protected function fp_js_template_selectlayout() {
$rv = '
<div class="file-picker fp-select">
<div class="fp-select-loading">
' . $this->pix_icon('i/loading_small', '') . '
</div>
<form class="form-horizontal">
<div class="fp-forminset">
<div class="fp-linktype-2 control-group control-radio clearfix">
<label class="control-label control-radio">'.get_string('makefileinternal', 'repository').'</label>
<div class="controls control-radio">
<input type="radio"/>
</div>
</div>
<div class="fp-linktype-1 control-group control-radio clearfix">
<label class="control-label control-radio">'.get_string('makefilelink', 'repository').'</label>
<div class="controls control-radio">
<input type="radio"/>
</div>
</div>
<div class="fp-linktype-4 control-group control-radio clearfix">
<label class="control-label control-radio">'.get_string('makefilereference', 'repository').'</label>
<div class="controls control-radio">
<input type="radio"/>
</div>
</div>
<div class="fp-linktype-8 control-group control-radio clearfix">
<label class="control-label control-radio">'.get_string('makefilecontrolledlink', 'repository').'</label>
<div class="controls control-radio">
<input type="radio"/>
</div>
</div>
<div class="fp-saveas control-group clearfix">
<label class="control-label">'.get_string('saveas', 'repository').'</label>
<div class="controls">
<input type="text"/>
</div>
</div>
<div class="fp-setauthor control-group clearfix">
<label class="control-label">'.get_string('author', 'repository').'</label>
<div class="controls">
<input type="text"/>
</div>
</div>
<div class="fp-setlicense control-group clearfix">
<label class="control-label">'.get_string('chooselicense', 'repository').'</label>
<div class="controls">
<select></select>
</div>
</div>
</div>
<div class="fp-select-buttons">
<button class="fp-select-confirm btn-primary btn">'.get_string('getfile', 'repository').'</button>
<button class="fp-select-cancel btn-cancel btn">'.get_string('cancel').'</button>
</div>
</form>
<div class="fp-info clearfix">
<div class="fp-hr"></div>
<p class="fp-thumbnail"></p>
<div class="fp-fileinfo">
<div class="fp-datemodified">'.get_string('lastmodified', 'repository').'<span class="fp-value"></span></div>
<div class="fp-datecreated">'.get_string('datecreated', 'repository').'<span class="fp-value"></span></div>
<div class="fp-size">'.get_string('size', 'repository').'<span class="fp-value"></span></div>
<div class="fp-license">'.get_string('license', 'repository').'<span class="fp-value"></span></div>
<div class="fp-author">'.get_string('author', 'repository').'<span class="fp-value"></span></div>
<div class="fp-dimensions">'.get_string('dimensions', 'repository').'<span class="fp-value"></span></div>
</div>
</div>
</div>';
return $rv;
return $this->render_from_template('core/filemanager_selectlayout', []);
}
/**
* FilePicker JS template for 'Upload file' repository
*
* Content to display when user chooses 'Upload file' repository (will be nested inside
* element with class 'fp-content').
*
* Must contain form (enctype="multipart/form-data" method="POST")
*
* The elements with the following classnames must be present:
* 'fp-file', 'fp-saveas', 'fp-setauthor', 'fp-setlicense'. Inside each of them must have
* one input element (or select in case of fp-setlicense). They may also have labels.
*
* Element with class 'fp-upload-btn' will hold onclick event for uploading the file;
*
* Please note that some fields may be hidden using CSS if this is part of quickupload form
*
* @return string
*/
protected function fp_js_template_uploadform() {
$rv = '
<div class="fp-upload-form">
<div class="fp-content-center">
<form enctype="multipart/form-data" method="POST" class="form-horizontal">
<div class="fp-formset">
<div class="fp-file control-group clearfix">
<label class="control-label">'.get_string('attachment', 'repository').'</label>
<div class="controls">
<input type="file"/>
</div>
</div>
<div class="fp-saveas control-group clearfix">
<label class="control-label">'.get_string('saveas', 'repository').'</label>
<div class="controls">
<input type="text"/>
</div>
</div>
<div class="fp-setauthor control-group clearfix">
<label class="control-label">'.get_string('author', 'repository').'</label>
<div class="controls">
<input type="text"/>
</div>
</div>
<div class="fp-setlicense control-group clearfix">
<label class="control-label">'.get_string('chooselicense', 'repository').'</label>
<div class="controls">
<select ></select>
</div>
</div>
</div>
</form>
<div class="mdl-align">
<button class="fp-upload-btn btn-primary btn">'.get_string('upload', 'repository').'</button>
</div>
</div>
</div> ';
return $rv;
return $this->render_from_template('core/filemanager_uploadform', []);
}
/**
@ -932,115 +464,29 @@ class core_files_renderer extends plugin_renderer_base {
/**
* FilePicker JS template for popup dialogue window asking for action when file with the same name already exists.
*
* Must have one top element, CSS for this element must define width and height of the window;
*
* content of element with class 'fp-dlg-text' will be replaced with dialog text;
* elements with classes 'fp-dlg-butoverwrite', 'fp-dlg-butrename',
* 'fp-dlg-butoverwriteall', 'fp-dlg-butrenameall' and 'fp-dlg-butcancel' will
* hold onclick events;
*
* content of element with class 'fp-dlg-butrename' will be substituted with appropriate string
* (Note that it may have long text)
*
* @return string
*/
protected function fp_js_template_processexistingfile() {
$rv = '
<div class="file-picker fp-dlg">
<p class="fp-dlg-text"></p>
<div class="fp-dlg-buttons">
<button class="fp-dlg-butoverwrite btn">'.get_string('overwrite', 'repository').'</button>
<button class="fp-dlg-butrename btn"></button>
<button class="fp-dlg-butcancel btn btn-cancel">'.get_string('cancel').'</button>
</div>
</div>';
return $rv;
return $this->render_from_template('core/filemanager_processexistingfile', []);
}
/**
* FilePicker JS template for popup dialogue window asking for action when file with the same name already exists (multiple-file version).
*
* Must have one top element, CSS for this element must define width and height of the window;
*
* content of element with class 'fp-dlg-text' will be replaced with dialog text;
* elements with classes 'fp-dlg-butoverwrite', 'fp-dlg-butrename' and 'fp-dlg-butcancel' will
* hold onclick events;
*
* content of element with class 'fp-dlg-butrename' will be substituted with appropriate string
* (Note that it may have long text)
* FilePicker JS template for popup dialogue window asking for action when file with the same name already exists
* (multiple-file version).
*
* @return string
*/
protected function fp_js_template_processexistingfilemultiple() {
$rv = '
<div class="file-picker fp-dlg">
<p class="fp-dlg-text"></p>
<a class="fp-dlg-butoverwrite fp-panel-button" href="#">'.get_string('overwrite', 'repository').'</a>
<a class="fp-dlg-butcancel fp-panel-button" href="#">'.get_string('cancel').'</a>
<a class="fp-dlg-butrename fp-panel-button" href="#"></a>
<br/>
<a class="fp-dlg-butoverwriteall fp-panel-button" href="#">'.get_string('overwriteall', 'repository').'</a>
<a class="fp-dlg-butrenameall fp-panel-button" href="#">'.get_string('renameall', 'repository').'</a>
</div>';
return $rv;
return $this->render_from_template('core/filemanager_processexistingfilemultiple', []);
}
/**
* FilePicker JS template for repository login form including templates for each element type
*
* Must contain one <form> element with templates for different input types inside:
* Elements with classes 'fp-login-popup', 'fp-login-textarea', 'fp-login-select' and
* 'fp-login-input' are templates for displaying respective login form elements. Inside
* there must be exactly one element with type <button>, <textarea>, <select> or <input>
* (i.e. fp-login-popup should have <button>, fp-login-textarea should have <textarea>, etc.);
* They may also contain the <label> element and it's content will be substituted with
* label;
*
* You can also define elements with classes 'fp-login-checkbox', 'fp-login-text'
* but if they are not found, 'fp-login-input' will be used;
*
* Element with class 'fp-login-radiogroup' will be used for group of radio inputs. Inside
* it should hava a template for one radio input (with class 'fp-login-radio');
*
* Element with class 'fp-login-submit' will hold on click mouse event (form submission). It
* will be removed if at least one popup element is present;
*
* @return string
*/
protected function fp_js_template_loginform() {
$rv = '
<div class="fp-login-form">
<div class="fp-content-center">
<form class="form-horizontal">
<div class="fp-formset">
<div class="fp-login-popup control-group clearfix">
<div class="controls fp-popup">
<button class="fp-login-popup-but btn-primary btn">'.get_string('login', 'repository').'</button>
</div>
</div>
<div class="fp-login-textarea control-group clearfix">
<div class="controls"><textarea></textarea></div>
</div>
<div class="fp-login-select control-group clearfix">
<label class="control-label"></label>
<div class="controls"><select></select></div>
</div>';
$rv .= '
<div class="fp-login-input control-group clearfix">
<label class="control-label"></label>
<div class="controls"><input/></div>
</div>
<div class="fp-login-radiogroup control-group clearfix">
<label class="control-label"></label>
<div class="controls fp-login-radio"><input /> <label></label></div>
</div>
</div>
<p><button class="fp-login-submit btn-primary btn">'.get_string('submit', 'repository').'</button></p>
</form>
</div>
</div>';
return $rv;
return $this->render_from_template('core/filemanager_loginform', []);
}
/**
@ -1066,13 +512,7 @@ class core_files_renderer extends plugin_renderer_base {
* Default contents is one text input field with name="s"
*/
public function repository_default_searchform() {
$searchinput = html_writer::label(get_string('searchrepo', 'repository'),
'reposearch', false, array('class' => 'accesshide'));
$searchinput .= html_writer::empty_tag('input', array('type' => 'text',
'id' => 'reposearch', 'name' => 's', 'value' => get_string('search', 'repository')));
$str = html_writer::tag('div', $searchinput, array('class' => "fp-def-search"));
return $str;
return $this->render_from_template('core/filemanager_default_searchform', []);
}
}

View File

@ -47,13 +47,11 @@
}
}}
<div class="gradingform_guide_comment_chooser" id="comment_chooser">
<ul role="list">
<div class="list-group">
{{#comments}}
<li role="listitem">
<button id="comment-option-{{criterionId}}-{{id}}" class="btn btn-link" tabindex="0">
{{description}}
</button>
</li>
<button class="list-group-item list-group-item-action" id="comment-option-{{criterionId}}-{{id}}" tabindex="0">
{{description}}
</button>
{{/comments}}
</ul>
</div>
</div>

View File

@ -46,50 +46,8 @@ class renderer extends \plugin_renderer_base {
* @return string HTML to display
*/
protected function render_user_button(user_button $button) {
$attributes = array('type' => 'button',
'class' => 'selectortrigger',
'value' => $button->label,
'disabled' => $button->disabled ? 'disabled' : null,
'title' => $button->tooltip);
if ($button->actions) {
$id = \html_writer::random_id('single_button');
$attributes['id'] = $id;
foreach ($button->actions as $action) {
$this->add_action_handler($action, $id);
}
}
// First the input element.
$output = \html_writer::empty_tag('input', $attributes);
// Then hidden fields.
$params = $button->url->params();
if ($button->method === 'post') {
$params['sesskey'] = sesskey();
}
foreach ($params as $var => $val) {
$output .= \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
}
// Then div wrapper for xhtml strictness.
$output = \html_writer::tag('div', $output);
// Now the form itself around it.
if ($button->method === 'get') {
$url = $button->url->out_omit_querystring(true); // Url without params, the anchor part allowed.
} else {
$url = $button->url->out_omit_querystring(); // Url without params, the anchor part not allowed.
}
if ($url === '') {
$url = '#'; // There has to be always some action.
}
$attributes = array('method' => $button->method,
'action' => $url,
'id' => $button->formid);
$output = \html_writer::tag('div', $output, $attributes);
// Finally one more wrapper with class.
return \html_writer::tag('div', $output, array('class' => $button->class));
$data = $button->export_for_template($this);
return $this->render_from_template('gradereport_history/user_button', $data);
}
/**

View File

@ -21,10 +21,10 @@
<input type="checkbox" name="{{applyname}}" value="1" id="{{applyname}}">
<label for="{{applyname}}">{{applylabel}}</label>
</div>
<fieldset>
<fieldset class="form-inline">
<legend class="accesshide">{{label}}</legend>
<label for="{{menuname}}">{{menulabel}}</label>
<select name="{{menuname}}" id="{{menuname}}">
<select name="{{menuname}}" id="{{menuname}}" class="form-control">
{{#menuoptions}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/menuoptions}}

View File

@ -17,4 +17,4 @@
{{!
Button.
}}
<input type="{{type}}" value={{#quote}}{{value}}{{/quote}}>
<input type="{{type}}" value={{#quote}}{{value}}{{/quote}} class="btn btn-secondary">

View File

@ -17,7 +17,7 @@
{{!
Dropdown attribute.
}}
<select id="{{name}}" name="{{name}}" tabindex="1" {{#disabled}}disabled{{/disabled}}>
<select id="{{name}}" name="{{name}}" class="custom-select" tabindex="1" {{#disabled}}disabled{{/disabled}}>
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/options}}

View File

@ -18,5 +18,5 @@
Text attribute.
}}
<label for="{{name}}" class="accesshide">{{label}}</label>
<input id="{{name}}" name="{{name}}" type="text" value="{{value}}" {{#tabindex}}tabindex="{{.}}"{{/tabindex}} {{#disabled}}disabled{{/disabled}}>
<input id="{{name}}" name="{{name}}" type="text" value="{{value}}" class="form-control" {{#tabindex}}tabindex="{{.}}"{{/tabindex}} {{#disabled}}disabled{{/disabled}}>
<input type="hidden" name="old{{name}}" value="{{value}}">

View File

@ -76,15 +76,18 @@ class behat_grade extends behat_base {
$gradeitem = behat_context_helper::escape($gradeitem);
if ($this->running_javascript()) {
$xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]//a[contains(@class,'toggle-display')]";
$xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]";
if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
$this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
$this->execute("behat_action_menu::i_open_the_action_menu_in",
array("//tr[contains(.,$gradeitem)]",
"xpath_element"));
}
}
$savechanges = get_string('savechanges', 'grades');
$edit = behat_context_helper::escape(get_string('edit') . ' ');
$linkxpath = "//a[./img[starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
$linkxpath = "//a[./*[contains(concat(' ', normalize-space(@class), ' '), ' icon ') " .
"and starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
$this->execute("behat_general::i_click_on", array($this->escape($linkxpath), "xpath_element"));
$this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
@ -128,16 +131,19 @@ class behat_grade extends behat_base {
$gradeitem = behat_context_helper::escape($gradeitem);
if ($this->running_javascript()) {
$xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]//a[contains(@class,'toggle-display')]";
$xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]";
if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
$this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
$this->execute("behat_action_menu::i_open_the_action_menu_in",
array("//tr[contains(.,$gradeitem)]",
"xpath_element"));
}
}
// Going to edit calculation.
$savechanges = get_string('savechanges', 'grades');
$edit = behat_context_helper::escape(get_string('editcalculation', 'grades'));
$linkxpath = "//a[./img[starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
$linkxpath = "//a[./*[contains(concat(' ', normalize-space(@class), ' '), ' icon ') " .
"and starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
$this->execute("behat_general::i_click_on", array($this->escape($linkxpath), "xpath_element"));
// Mapping names to idnumbers.
@ -145,11 +151,12 @@ class behat_grade extends behat_base {
foreach ($datahash as $gradeitem => $idnumber) {
// This xpath looks for course, categories and items with the provided name.
// Grrr, we can't equal in categoryitem and courseitem because there is a line jump...
$inputxpath ="//input[@class='idnumber'][" .
"parent::li[@class='item'][text()='" . $gradeitem . "']" .
" or " .
"parent::li[@class='categoryitem' or @class='courseitem']/parent::ul/parent::li[starts-with(text(),'" . $gradeitem . "')]" .
"]";
$inputxpath = "//input[@class='idnumber'][" .
"parent::li[@class='item'][text()='" . $gradeitem . "']" .
" or " .
"parent::li[@class='categoryitem' or @class='courseitem']" .
"/parent::ul/parent::li[starts-with(text(),'" . $gradeitem . "')]" .
"]";
$this->execute('behat_forms::i_set_the_field_with_xpath_to', array($inputxpath, $idnumber));
}
@ -174,17 +181,18 @@ class behat_grade extends behat_base {
$gradeitem = behat_context_helper::escape($gradeitem);
if ($this->running_javascript()) {
$xpath = "//tr[contains(.,$gradecategorytotal)]//*[contains(@class,'moodle-actionmenu')]" .
"//a[contains(@class,'toggle-display')]";
$xpath = "//tr[contains(.,$gradecategorytotal)]//*[contains(@class,'moodle-actionmenu')]";
if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
$this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
$xpath = "//tr[contains(.,$gradecategorytotal)]";
$this->execute("behat_action_menu::i_open_the_action_menu_in", array($xpath, "xpath_element"));
}
}
// Going to edit calculation.
$savechanges = get_string('savechanges', 'grades');
$edit = behat_context_helper::escape(get_string('editcalculation', 'grades'));
$linkxpath = "//a[./img[starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
$linkxpath = "//a[./*[contains(concat(' ', normalize-space(@class), ' '), ' icon ') " .
"and starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
$this->execute("behat_general::i_click_on", array($this->escape($linkxpath), "xpath_element"));
// Mapping names to idnumbers.
@ -193,11 +201,11 @@ class behat_grade extends behat_base {
// This xpath looks for course, categories and items with the provided name.
// Grrr, we can't equal in categoryitem and courseitem because there is a line jump...
$inputxpath = "//input[@class='idnumber'][" .
"parent::li[@class='item'][text()='" . $gradeitem . "']" .
" | " .
"parent::li[@class='categoryitem' | @class='courseitem']" .
"/parent::ul/parent::li[starts-with(text(),'" . $gradeitem . "')]" .
"]";
"parent::li[@class='item'][text()='" . $gradeitem . "']" .
" | " .
"parent::li[@class='categoryitem' | @class='courseitem']" .
"/parent::ul/parent::li[starts-with(text(),'" . $gradeitem . "')]" .
"]";
$this->execute('behat_forms::i_set_the_field_with_xpath_to', array($inputxpath, $idnumber));
}
@ -221,9 +229,10 @@ class behat_grade extends behat_base {
if ($this->running_javascript()) {
$gradeitemliteral = behat_context_helper::escape($gradeitem);
$xpath = "//tr[contains(.,$gradeitemliteral)]//*[contains(@class,'moodle-actionmenu')]//a[contains(@class,'toggle-display')]";
$xpath = "//tr[contains(.,$gradeitemliteral)]//*[contains(@class,'moodle-actionmenu')]";
if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
$this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
$xpath = "//tr[contains(.,$gradeitemliteral)]";
$this->execute("behat_action_menu::i_open_the_action_menu_in", array($xpath, "xpath_element"));
}
}
@ -290,11 +299,10 @@ class behat_grade extends behat_base {
* @param string $gradepath
*/
public function i_navigate_to_in_the_course_gradebook($gradepath) {
// If we are not on one of the gradebook pages already, follow "Grades" link in the navigation block.
// If we are not on one of the gradebook pages already, follow "Grades" link in the navigation drawer.
$xpath = '//div[contains(@class,\'grade-navigation\')]';
if (!$this->getSession()->getPage()->findAll('xpath', $xpath)) {
$this->execute("behat_general::i_click_on_in_the", array(get_string('grades'), 'link',
get_string('pluginname', 'block_navigation'), 'block'));
$this->execute('behat_navigation::i_select_from_flat_navigation_drawer', get_string('grades'));
}
$this->select_in_gradebook_tabs($gradepath);

View File

@ -194,15 +194,11 @@ class core_grade_import_lib_test extends advanced_testcase {
'itemid' => $gradeitem->id
));
$url = $CFG->wwwroot . '/grade/index.php';
$expectedresponse = "++ Grade import success ++
<div class=\"continuebutton\"><form method=\"get\" action=\"$url\"><div><input type=\"submit\" value=\"Continue\" /><input type=\"hidden\" name=\"id\" value=\"$course->id\" /></div></form></div>";
ob_start();
$status = grade_import_commit($course->id, $importcode);
$output = ob_get_contents();
ob_end_clean();
$this->assertTrue($status);
$this->assertEquals($expectedresponse, $output);
$this->assertContains("++ Grade import success ++", $output);
}
}

View File

@ -55,11 +55,6 @@ class behat_config_util {
*/
private $themecontexts;
/**
* @var array list of all contexts in theme suite.
*/
private $themesuitecontexts;
/**
* @var array list of overridden theme contexts.
*/
@ -314,13 +309,16 @@ class behat_config_util {
$this->contexts = array();
foreach ($components as $componentname => $componentpath) {
if (false !== strpos($componentname, 'theme_')) {
continue;
}
$componentpath = self::clean_path($componentpath);
if (!file_exists($componentpath . self::get_behat_tests_path())) {
continue;
}
$diriterator = new DirectoryIterator($componentpath . self::get_behat_tests_path());
$regite = new RegexIterator($diriterator, '|behat_.*\.php$|');
$regite = new RegexIterator($diriterator, '|^behat_.*\.php$|');
// All behat_*.php inside self::get_behat_tests_path() are added as steps definitions files.
foreach ($regite as $file) {
@ -493,24 +491,23 @@ class behat_config_util {
$suites = $this->get_behat_suites($parallelruns, $currentrun);
$overriddenthemescontexts = $this->get_overridden_theme_contexts();
if (!empty($overriddenthemescontexts)) {
$allcontexts = array_merge($this->contexts, $overriddenthemescontexts);
} else {
$allcontexts = $this->contexts;
}
// Remove selectors from step definitions.
$themes = $this->get_list_of_themes();
$selectortypes = ['named_partial', 'named_exact'];
foreach ($themes as $theme) {
$allpaths = [];
foreach (array_keys($suites) as $theme) {
// Remove selectors from step definitions.
foreach ($selectortypes as $selectortype) {
// Don't include selector classes.
$selectorclass = self::get_behat_theme_selector_override_classname($theme, $selectortype);
if (isset($allcontexts[$selectorclass])) {
unset($allcontexts[$selectorclass]);
if (isset($suites[$theme]['contexts'][$selectorclass])) {
unset($suites[$theme]['contexts'][$selectorclass]);
}
}
// Get a list of all step definition paths.
$allpaths = array_merge($allpaths, $suites[$theme]['contexts']);
// Convert the contexts array to a list of names only.
$suites[$theme]['contexts'] = array_keys($suites[$theme]['contexts']);
}
// Comments use black color, so failure path is not visible. Using color other then black/white is safer.
@ -532,7 +529,7 @@ class behat_config_util {
),
'Moodle\BehatExtension' => array(
'moodledirroot' => $CFG->dirroot,
'steps_definitions' => $allcontexts,
'steps_definitions' => $allpaths,
)
)
)
@ -1128,9 +1125,8 @@ class behat_config_util {
// Create list of theme suite features and contexts.
foreach ($themes as $theme) {
// Get theme features.
// Get theme features and contexts.
$themefeatures[$theme] = $this->get_behat_features_for_theme($theme);
$themecontexts[$theme] = $this->get_behat_contexts_for_theme($theme);
}
@ -1141,20 +1137,6 @@ class behat_config_util {
}
}
// Remove list of theme contexts form other suite contexts, as suite don't require other theme specific contexts.
foreach ($themecontexts as $themename => $themecontext) {
if (!empty($themecontext['contexts'])) {
foreach ($themecontext['contexts'] as $contextkey => $contextpath) {
// Remove theme specific contexts from other themes.
foreach ($themes as $currenttheme) {
if (($currenttheme != $themename) && isset($themecontexts[$currenttheme]['suitecontexts'][$contextkey])) {
unset($themecontexts[$currenttheme]['suitecontexts'][$contextkey]);
}
}
}
}
}
// Set suite for each theme.
$suites = array();
foreach ($themes as $theme) {
@ -1186,12 +1168,12 @@ class behat_config_util {
$suitename = $theme;
}
// Add suite no matter what. If there is no feature in suite then it will just exist successfully with no
// scenarios. But if we don't set this then the user has to know which run doesn't have suite and which run do.
// Add suite no matter what. If there is no feature in suite then it will just exist successfully with no scenarios.
// But if we don't set this then the user has to know which run doesn't have suite and which run do.
$suites = array_merge($suites, array(
$suitename => array(
'paths' => array_values($themesuitefeatures),
'contexts' => array_keys($themecontexts[$theme]['suitecontexts']),
'contexts' => $themecontexts[$theme],
)
));
}
@ -1223,7 +1205,7 @@ class behat_config_util {
foreach ($themes as $themename => $themedir) {
// Load the theme config.
try {
$theme = theme_config::load($themename);
$theme = $this->get_theme_config($themename);
} catch (Exception $e) {
// Bad theme, just skip it for now.
continue;
@ -1243,10 +1225,21 @@ class behat_config_util {
return $selectablethemes;
}
/**
* Return the theme config for a given theme name.
* This is done so we can mock it in PHPUnit.
*
* @param $themename name of theme
* @return theme_config
*/
public function get_theme_config($themename) {
return theme_config::load($themename);
}
/**
* Return theme directory.
*
* @param string $themename
* @param string $themename name of theme
* @return string theme directory
*/
protected function get_theme_test_directory($themename) {
@ -1339,7 +1332,7 @@ class behat_config_util {
$tests = array();
$testtypes = array(
'contexts' => '|behat_.*\.php$|',
'contexts' => '|^behat_.*\.php$|',
'features' => '|.*\.feature$|',
);
@ -1427,91 +1420,71 @@ class behat_config_util {
return $retval;
}
/**
* Return list of contexts overridden by themes.
*
* @return array.
*/
protected function get_overridden_theme_contexts() {
if (empty($this->overriddenthemescontexts)) {
$this->overriddenthemescontexts = array();
}
return $this->overriddenthemescontexts;
}
/**
* Return list of behat contexts for theme and update $this->stepdefinitions list.
*
* @param string $theme theme name.
* @return array list($themecontexts, $themesuitecontexts)
* @return List of contexts
*/
protected function get_behat_contexts_for_theme($theme) {
protected function get_behat_contexts_for_theme($theme) : array {
// If we already have this list then just return. This will not change by run.
if (!empty($this->themecontexts[$theme]) && !empty($this->themesuitecontexts)) {
return array(
'contexts' => $this->themecontexts[$theme],
'suitecontexts' => $this->themesuitecontexts[$theme],
);
if (!empty($this->themecontexts[$theme])) {
return $this->themecontexts[$theme];
}
if (empty($this->overriddenthemescontexts)) {
$this->overriddenthemescontexts = array();
try {
$themeconfig = $this->get_theme_config($theme);
} catch (Exception $e) {
// This theme has no theme config.
return [];
}
$contexts = $this->get_components_contexts();
// The theme will use all core contexts, except the one overridden by theme or its parent.
$parentcontexts = [];
if (isset($themeconfig->parents)) {
foreach ($themeconfig->parents as $parent) {
if ($parentcontexts = $this->get_behat_contexts_for_theme($parent)) {
break;
}
}
}
// Create list of contexts used by theme suite.
$themecontexts = $this->get_tests_for_theme($theme, 'contexts');
if (empty($parentcontexts)) {
$parentcontexts = $this->get_components_contexts();
}
// Remove contexts which have been actively blacklisted.
$blacklistedcontexts = $this->get_blacklisted_tests_for_theme($theme, 'contexts');
// Theme suite will use all core contexts, except the one overridden by theme.
$themesuitecontexts = $contexts;
foreach ($themecontexts as $context => $path) {
// If a context in theme starts with behat_theme_{themename}_behat_* then it's overriding core context.
if (preg_match('/^behat_theme_'.$theme.'_(\w+)$/', $context, $match)) {
if (!empty($themesuitecontexts[$match[1]])) {
unset($themesuitecontexts[$match[1]]);
}
// Add this to the list of overridden paths, so it can be added to final contexts list for class resolver.
$this->overriddenthemescontexts[$context] = $path;
}
$selectortypes = ['named_partial', 'named_exact'];
foreach ($selectortypes as $selectortype) {
// Don't include selector classes.
if ($context === self::get_behat_theme_selector_override_classname($theme, $selectortype)) {
unset($this->contexts[$context]);
unset($themesuitecontexts[$context]);
continue;
}
}
// Add theme specific contexts with suffix to steps definitions.
$themesuitecontexts[$context] = $path;
}
// Remove blacklisted contexts.
foreach ($blacklistedcontexts as $blacklistpath) {
$blacklistcontext = basename($blacklistpath, '.php');
unset($themesuitecontexts[$blacklistcontext]);
unset($parentcontexts[$blacklistcontext]);
}
// We are only interested in the class name of context.
$this->themesuitecontexts[$theme] = $themesuitecontexts;
$this->themecontexts[$theme] = $themecontexts;
// Apply overrides.
$contexts = array_merge($parentcontexts, $this->get_tests_for_theme($theme, 'contexts'));
$retval = array(
'contexts' => $themecontexts,
'suitecontexts' => $themesuitecontexts,
);
// Remove classes which are overridden.
foreach ($contexts as $contextclass => $path) {
require_once($path);
if (!class_exists($contextclass)) {
// This may be a Poorly named class.
continue;
}
return $retval;
$rc = new \ReflectionClass($contextclass);
while ($rc = $rc->getParentClass()) {
if (isset($contexts[$rc->name])) {
unset($contexts[$rc->name]);
}
}
}
// Sort the list of contexts.
ksort($contexts);
$this->themecontexts[$theme] = $contexts;
return $contexts;
}
}

View File

@ -90,6 +90,13 @@ class behat_context_helper {
* @return behat_base
*/
public static function get($classname) {
$contexts = self::$environment->getContexts();
foreach ($contexts as $context) {
if (is_a($context, $classname)) {
return $context;
}
}
$suitename = self::$environment->getSuite()->getName();
// If default suite, then get the default theme name.

View File

@ -17,9 +17,8 @@
/**
* Files interactions with behat.
*
* Note that steps definitions files can not extend other steps definitions files, so
* steps definitions which makes use of file attachments or filepicker should
* extend behat_files instead of behat_base.
* Note that steps definitions files can not extend other steps definitions files, so steps definitions which makes use
* of file attachments or filepicker should use this behat_file_helper trait.
*
* @package core
* @category test
@ -37,16 +36,15 @@ use Behat\Mink\Exception\ExpectationException as ExpectationException,
/**
* Files-related actions.
*
* Steps definitions related with filepicker or repositories should extend
* this class instead of behat_base as it provides useful methods to deal
* with the common filepicker issues.
* Steps definitions related with filepicker or repositories should extend use this trait as it provides useful methods
* to deal with the common filepicker issues.
*
* @package core
* @category test
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_files extends behat_base {
trait core_behat_file_helper {
/**
* Gets the NodeElement for filepicker of filemanager moodleform element.
@ -58,6 +56,7 @@ class behat_files extends behat_base {
* @param string $filepickerelement The filepicker form field label
* @return NodeElement The hidden element node.
*/
protected function get_filepicker_node($filepickerelement) {
// More info about the problem (in case there is a problem).
@ -66,18 +65,18 @@ class behat_files extends behat_base {
// If no file picker label is mentioned take the first file picker from the page.
if (empty($filepickerelement)) {
$filepickercontainer = $this->find(
'xpath',
"//*[@data-fieldtype=\"filemanager\"]",
$exception
'xpath',
"//*[@data-fieldtype=\"filemanager\"]",
$exception
);
} else {
// Gets the ffilemanager node specified by the locator which contains the filepicker container.
$filepickerelement = behat_context_helper::escape($filepickerelement);
$filepickercontainer = $this->find(
'xpath',
"//input[./@id = //label[normalize-space(.)=$filepickerelement]/@for]" .
'//ancestor::div[@data-fieldtype="filemanager" or @data-fieldtype="filepicker"]',
$exception
'xpath',
"//input[./@id = //label[normalize-space(.)=$filepickerelement]/@for]" .
"//ancestor::*[@data-fieldtype = 'filemanager' or @data-fieldtype = 'filepicker']",
$exception
);
}
@ -275,5 +274,4 @@ class behat_files extends behat_base {
$filepickernode
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -30,31 +30,26 @@
}
}
}}
<div class="{{classes}}"{{#attributes}} {{name}}="{{value}}"{{/attributes}}>
<div class="action-menu {{classes}} d-inline"{{#attributes}} {{name}}="{{value}}"{{/attributes}}>
{{#primary}}
<ul class="{{classes}}"{{#attributes}} {{name}}="{{value}}"{{/attributes}}>
<div class="{{classes}} d-flex "{{#attributes}} {{name}}="{{value}}"{{/attributes}}>
{{#prioritise}}<li role="presentation">{{> core/action_menu_trigger }}</li>{{/prioritise}}<!--
{{#prioritise}}{{> core/action_menu_trigger }}{{/prioritise}}
-->{{#items}}<li role="presentation">{{> core/action_menu_item }}</li>{{/items}}<!--
{{#items}}
<div class="action-menu-item">
{{> core/action_menu_item }}
</div>
{{/items}}
-->{{^prioritise}}<li role="presentation">{{> core/action_menu_trigger }}</li>{{/prioritise}}
{{^prioritise}}
<div class="action-menu-trigger">
{{> core/action_menu_trigger }}
</div>
{{/prioritise}}
</ul>
</div>
{{/primary}}
{{#secondary}}
<ul class="{{classes}}"{{#attributes}} {{name}}="{{value}}"{{/attributes}}>
{{#items}}<li role="presentation">{{> core/action_menu_item }}</li>{{/items}}
</ul>
{{/secondary}}
</div>
{{#js}}
require(['core/yui'], function(Y) {
Y.use('moodle-core-actionmenu', function() {
M.core.actionmenu.init();
});
});
{{/js}}

View File

@ -23,14 +23,12 @@
{
"text": "Example link text",
"showtext": true,
"url": "http://example.com/link",
"classes": "menu-action",
"instance": "1"
"url": "http://example.com/link"
}
}}
{{^disabled}}
<a href="{{url}}" class="{{classes}}" {{#attributes}}{{name}}={{#quote}}{{value}}{{/quote}} {{/attributes}}{{#showtext}}aria-labelledby="actionmenuaction-{{instance}}"{{/showtext}}>{{#icon}}{{#pix}}{{key}}, {{component}}, {{title}}{{/pix}}{{/icon}}{{#showtext}}<span class="menu-action-text" id="actionmenuaction-{{instance}}">{{{text}}}</span>{{/showtext}}</a>
{{/disabled}}
{{#disabled}}
<span class="currentlink" role="menuitem">{{#icon}}{{#pix}}{{key}}, {{component}}, {{title}}{{/pix}}{{/icon}}{{{text}}}</span>
<span class="currentlink" role="menuitem">{{#icon}}{{#pix}}{{key}},{{component}},{{title}}{{/pix}}{{/icon}}{{{text}}}</span>
{{/disabled}}

View File

@ -21,10 +21,100 @@
Example context (json):
{
"text": "Example link text",
"title": "Example link title",
"url": "http://example.com/link",
"classes": "icon menu-action",
"instance": "1",
"title": "Trigger me!",
"menutrigger": true,
"triggerextraclasses": ""
"triggerextraclasses": "",
"attributes": [
{"name": "role", "value": "menuitem" },
{"name": "data-title", "value": "mymoodle,admin" }
],
"secondary": {
"classes": "menu align-tr-br",
"attributes": [
{"name": "id", "value": "action-menu-0-menu"},
{"name": "data-rel", "value": "menu-content"},
{"name": "role", "value": "menu"},
{"name": "data-align", "value": "tr-br"}
],
"items": [
{"actionmenulink":
{
"id": "action_link59ecf8394a68078",
"disabled": false,
"text": "Dashboard",
"url": "http://example.com/link",
"icon": {
"key": "i/dashboard",
"component": null,
"title": "Dashboard"
},
"classes": "icon menu-action",
"attributes": [
{"name": "role", "value": "menuitem"},
{"name": "data-title", "value": "mymoodle,admin"}
],
"instance": 1,
"showtext": true
}
},
{"actionmenufiller":
{
"id": "action_link59ecf8394a68079",
"disabled": false,
"text": "",
"url": "",
"icon": null,
"classes": "",
"attributes": [
{"name": "role", "value": "menuitem"}
]
}
}
]
}
}
}}
<a href="#" class="{{triggerextraclasses}} toggle-display {{#menutrigger}}textmenu{{/menutrigger}}" id="action-menu-toggle-{{instance}}" title="{{title}}" role="menuitem">{{{actiontext}}}{{{menutrigger}}}{{#icon}}{{#pix}}{{key}}, {{component}}, {{title}}{{/pix}}{{/icon}}{{#rawicon}}{{{.}}}{{/rawicon}}{{#menutrigger}}<b class="caret"></b>{{/menutrigger}}</a>
<div class="dropdown">
<a href="#" tabindex="0" class="{{triggerextraclasses}} dropdown-toggle icon-no-margin" id="dropdown-{{instance}}" aria-label="{{title}}" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" aria-controls="action-menu-{{instance}}-menu">
{{{actiontext}}}
{{{menutrigger}}}
{{#icon}}
{{#pix}}
{{key}},{{component}},{{title}}
{{/pix}}
{{/icon}}
{{#rawicon}}{{{.}}}{{/rawicon}}
{{#menutrigger}}
<b class="caret"></b>
{{/menutrigger}}
</a>
{{#secondary}}
<div class="dropdown-menu dropdown-menu-right {{classes}}"{{#attributes}} {{name}}="{{value}}"{{/attributes}} id="dropdown-menu-{{instance}}">
{{#items}}
{{#actionmenulink}}
<a href="{{url}}" class="dropdown-item {{classes}}" {{#attributes}}{{name}}={{#quote}}{{value}}{{/quote}} {{/attributes}}{{#showtext}}aria-labelledby="actionmenuaction-{{instance}}"{{/showtext}}>
{{#icon}}
{{#pix}}{{key}},{{component}},{{title}}{{/pix}}
{{/icon}}
{{#showtext}}
<span class="menu-action-text" id="actionmenuaction-{{instance}}">
{{{text}}}
</span>
{{/showtext}}
</a>
{{/actionmenulink}}
{{#actionmenufiller}}
<div class="dropdown-divider" role="presentation"><span class="filler">&nbsp;</span></div>
{{/actionmenufiller}}
{{^actionmenulink}}
{{^actionmenufiller}}
<div class="dropdown-item">{{> core/action_menu_item }}</div>
{{/actionmenufiller}}
{{/actionmenulink}}
{{/items}}
</div>
{{/secondary}}
</div>

View File

@ -26,12 +26,33 @@
"homelink": "/"
}
}}
<h3>{{#str}}considereddigitalminor{{/str}}</h3>
<p class="m-b-0">{{#str}}digitalminor_desc{{/str}}</p>
<div class="p-t-1 p-b-1">
<p class="m-b-0">{{{supportname}}}</p>
<p class="m-b-0">{{{supportemail}}}</p>
</div>
<div class="backlink">
<a href="{{homelink}}">{{#str}}backtohome{{/str}}</a>
<div class="container-fluid mt-1 mt-md-5">
<div class="row justify-content-md-center">
<div class="col-md-8 push-md-2 col-xl-6 push-xl-3">
<div class="card">
<div class="card-body">
<div class="card-title text-xs-center">
{{#logourl}}
<h2><img src="{{logourl}}" title="{{sitename}}" alt="{{sitename}}"/></h2>
{{/logourl}}
{{^logourl}}
<h2>{{sitename}}</h2>
{{/logourl}}
<hr>
</div>
<div class="card-title">
<h3>{{#str}}considereddigitalminor{{/str}}</h3>
</div>
<div class="p-t-1 p-b-2">
<p>{{#str}}digitalminor_desc{{/str}}</p>
<p class="m-b-0">{{{supportname}}}</p>
<p class="m-b-0">{{{supportemail}}}</p>
</div>
<div class="backlink">
<a href="{{homelink}}">{{#str}}backtohome{{/str}}</a>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -25,13 +25,40 @@
"formhtml": "(Form html would go here)"
}
}}
{{#error}}
<div class="alert alert-danger" role="alert">
{{{error}}}
<div class="container-fluid mt-1 mt-md-5">
<div class="row justify-content-md-center">
<div class="col-md-8 push-md-2 col-xl-6 push-xl-3">
<div class="card">
<div class="card-body">
<div class="card-title text-xs-center">
{{#logourl}}
<h2><img src="{{logourl}}" title="{{sitename}}" alt="{{sitename}}"/></h2>
{{/logourl}}
{{^logourl}}
<h2>{{sitename}}</h2>
{{/logourl}}
<hr>
</div>
{{#error}}
<div class="alert alert-danger" role="alert" data-aria-autofocus="true">
{{{error}}}
</div>
{{/error}}
<div class="card-title">
<h3>{{#str}}agelocationverification{{/str}}</h3>
</div>
<div class="m-t-2 m-b-2">
{{{formhtml}}}
</div>
<hr>
<div class="card-title">
<h3>{{#str}}whyisthisrequired{{/str}}</h3>
</div>
<div class="m-t-1">
<p >{{#str}}explanationdigitalminor{{/str}}</p>
</div>
</div>
</div>
</div>
</div>
{{/error}}
<h3>{{#str}}agelocationverification{{/str}}</h3>
{{{formhtml}}}
<hr>
<h3>{{#str}}whyisthisrequired{{/str}}</h3>
<p >{{#str}}explanationdigitalminor{{/str}}</p>
</div>

View File

@ -17,7 +17,12 @@
{{!
@template core/availability_info
Moodle template for the course or section availability information.
Renders the availability info on the course outline page.
Availability info can be displayed for activity modules or whole course
sections. Activity modules can be either hidden from students, or available
but not shown on course page (stealth), or the access can be restricted by
configured conditions. Sections can be hidden.
Classes required for JS:
* none
@ -25,9 +30,32 @@
Data attributes required for JS:
* none
Context variables required for this template:
* classes String list of CSS classes for the wrapping element
* text HTML formatted text with the actual availability information
* ishidden Boolean flag indiciating that the item is hidden from students
* isstealth Boolean flag indicating that the item is in stealth mode
* isrestricted Boolean flag indicating that restricted access conditions apply
* isfullinfo Boolean flag indicating that the full list of restricted
access conditions is displayed (aka teacher's view).
Example context (json):
{ "classes": "", "text": "This activity is not available" }
{
"classes": "",
"text": "Not available unless: <ul><li>It is on or after <strong>8 June 2012</strong></li></ul>",
"ishidden": 0,
"isstealth": 0,
"isrestricted": 1,
"isfullinfo": 1
}
}}
{{#text}}
<div class="availabilityinfo {{classes}}">{{{text}}}</div>
<div class="availabilityinfo {{classes}}">
{{^isrestricted}}
<span class="tag tag-info">{{{text}}}</span>
{{/isrestricted}}
{{#isrestricted}}
<span class="tag tag-info">{{#str}}restricted, core{{/str}}</span> {{{text}}}
{{/isrestricted}}
</div>
{{/text}}

View File

@ -70,8 +70,8 @@
</div>
<div class="submitbuttons">
<input type="submit" name="submitbutton" class="submitbutton" value={{#quote}}{{#str}}add{{/str}}{{/quote}}>
<input type="submit" name="addcancel" class="addcancel" value={{#quote}}{{#str}}cancel{{/str}}{{/quote}}>
<input type="submit" name="submitbutton" class="submitbutton btn btn-primary" value={{#quote}}{{#str}}add{{/str}}{{/quote}}>
<input type="submit" name="addcancel" class="addcancel btn btn-secondary" value={{#quote}}{{#str}}cancel{{/str}}{{/quote}}>
</div>
</form>

View File

@ -41,8 +41,8 @@
}
}}
<div class="row-fluid rtl-compatible">
<div class="span4">{{$ column1 }}{{{ col1content }}}{{/ column1 }}</div>
<div class="span4">{{$ column2 }}{{{ col2content }}}{{/ column2 }}</div>
<div class="span4">{{$ column3 }}{{{ col3content }}}{{/ column3 }}</div>
<div class="row">
<div class="col-md-4">{{$ column1 }}{{{ col1content }}}{{/ column1 }}</div>
<div class="col-md-4">{{$ column2 }}{{{ col2content }}}{{/ column2 }}</div>
<div class="col-md-4">{{$ column3 }}{{{ col3content }}}{{/ column3 }}</div>
</div>

View File

@ -39,7 +39,7 @@
}
}}
<div class="row-fluid rtl-compatible">
<div class="span4">{{$ column1 }}{{{ col1content }}}{{/ column1 }}</div>
<div class="span8">{{$ column2 }}{{{ col2content }}}{{/ column2 }}</div>
<div class="row">
<div class="col-md-4">{{$ column1 }}{{{ col1content }}}{{/ column1 }}</div>
<div class="col-md-8">{{$ column2 }}{{{ col2content }}}{{/ column2 }}</div>
</div>

View File

@ -38,7 +38,7 @@
"col2content": "<div class='alert alert-success'>2. Vivamus ac orci in velit fringilla aliquam a a nisl. Cras luctus quam laoreet magna pulvinar aliquet.</div>"
}
}}
<div class="row-fluid rtl-compatible">
<div class="span8">{{$ column1 }}{{{ col1content }}}{{/ column1 }}</div>
<div class="span4">{{$ column2 }}{{{ col2content }}}{{/ column2 }}</div>
<div class="row">
<div class="col-md-8">{{$ column1 }}{{{ col1content }}}{{/ column1 }}</div>
<div class="col-md-4">{{$ column2 }}{{{ col2content }}}{{/ column2 }}</div>
</div>

View File

@ -17,8 +17,6 @@
{{!
@template core/dataformat_selector
Template for dataformat selection and download form.
Context variables required for this template:
* label
* base
@ -32,24 +30,26 @@
{
"base": "http://example.org/",
"name": "test",
"value": "test",
"label": "Download table data as",
"params": false,
"options": [{"label": "CSV", "name": "csv"}, {"label": "Excel", "name": "excel"}],
"submit": "Download"
"submit": "Download",
"sesskey": ""
}
}}
<form method="get" action="{{base}}" class="dataformatselector">
<div class="mdl-align">
<form method="get" action="{{base}}" class="dataformatselector m-1">
<div class="form-inline text-xs-right">
<input type="hidden" name="sesskey" value="{{sesskey}}">
<label for="downloadtype_{{name}}">{{label}}</label>
<select name="{{name}}" id="downloadtype_{{name}}">
{{#options}}
<option value="{{value}}">{{label}}</option>
{{/options}}
<label for="downloadtype_{{name}}" class="mr-1">{{label}}</label>
<select name="{{name}}" id="downloadtype_{{name}}" class="form-control">
{{#options}}
<option value="{{value}}">{{label}}</option>
{{/options}}
</select>
<input type="submit" value="{{submit}}">
<button type="submit" class="btn btn-secondary">{{submit}}</button>
{{#params}}
<input type="hidden" name="{{name}}" value="{{value}}" />
<input type="hidden" name="{{name}}" value="{{value}}">
{{/params}}
</div>
</form>

View File

@ -36,7 +36,7 @@
{ "inputID": 1, "suggestionsId": 2, "selectionId": 3, "downArrowId": 4, "placeholder": "Select something" }
}}
{{#showSuggestions}}
<input type="text" id="{{inputId}}" list="{{suggestionsId}}" placeholder="{{placeholder}}" role="combobox" aria-expanded="false" autocomplete="off" autocorrect="off" autocapitalize="off" aria-autocomplete="list" aria-owns="{{suggestionsId}} {{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/><span class="form-autocomplete-downarrow" id="{{downArrowId}}">&#x25BC;</span>
<input type="text" id="{{inputId}}" class="form-control" list="{{suggestionsId}}" placeholder="{{placeholder}}" role="combobox" aria-expanded="false" autocomplete="off" autocorrect="off" autocapitalize="off" aria-autocomplete="list" aria-owns="{{suggestionsId}} {{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/><span class="form-autocomplete-downarrow" id="{{downArrowId}}">&#x25BC;</span>
{{/showSuggestions}}
{{^showSuggestions}}
<input type="text" id="{{inputId}}" placeholder="{{placeholder}}" role="textbox" aria-owns="{{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/>

View File

@ -37,15 +37,14 @@
{ "label": "Another item label with <strong>tags</strong>", "value": "4" }
], "noSelectionString": "No selection" }
}}
<div class="form-autocomplete-selection {{#multiple}}form-autocomplete-multiple{{/multiple}}" id="{{selectionId}}" role="list" aria-atomic="true" {{#multiple}}tabindex="0" aria-multiselectable="true"{{/multiple}}>
<div class="form-autocomplete-selection w-100 {{#multiple}}form-autocomplete-multiple{{/multiple}}" id="{{selectionId}}" role="list" aria-atomic="true" {{#multiple}}tabindex="0" aria-multiselectable="true"{{/multiple}}>
<span class="accesshide">{{#str}}selecteditems, form{{/str}}</span>
{{#items}}
<span role="listitem" data-value="{{value}}" aria-selected="true" class="label label-info" style="font-size: 100%; margin-bottom: 0.5rem;">
<span role="listitem" data-value="{{value}}" aria-selected="true" class="tag tag-info mb-3 mr-1" style="font-size: 100%">
{{#multiple}}<span aria-hidden="true">× </span>{{/multiple}}{{{label}}}
</span>
{{/items}}
{{^items}}
<span>{{noSelectionString}}</span>
<span class="mb-3 mr-1">{{noSelectionString}}</span>
{{/items}}
</div>
</div>

View File

@ -1,19 +1,3 @@
{{!
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/>.
}}
{{!
@template core/help_icon
@ -33,6 +17,9 @@
}
}
}}
<span class="helptooltip">
<a href="{{url}}" title={{#quote}}{{title}}{{/quote}} aria-haspopup="true" target="_blank">{{#icon}}{{>core/pix_icon}}{{/icon}}{{#linktext}}{{.}}{{/linktext}}</a>
</span>
<a class="btn btn-link p-0" role="button"
data-container="body" data-toggle="popover"
data-placement="{{#ltr}}right{{/ltr}}{{^ltr}}left{{/ltr}}" data-content="{{text}} {{completedoclink}}"
data-html="true" tabindex="0" data-trigger="focus">
{{#pix}}help, core, {{{alt}}}{{/pix}}
</a>

View File

@ -73,24 +73,30 @@
]
}
}}
<div class="initialbar {{class}}">
<span class="initialbarlabel">{{title}}</span>
{{#current}}
<a class="initialbarall page-item letter" href="{{url}}">{{all}}</a>
{{/current}}
{{^current}}
<div class="initialbarall letter page-item active">{{all}}</div>
{{/current}}
<div class="initialbar {{class}} d-flex flex-wrap justify-content-center justify-content-md-start">
<span class="initialbarlabel mr-2">{{title}}</span>
<div class="initialbargroups">
<div class="initialbargroups d-flex flex-wrap justify-content-center justify-content-md-start">
<ul class="pagination pagination-sm">
{{#current}}
<li class="initialbarall page-item">
<a class="page-link" href="{{url}}">{{all}}</a>
</li>
{{/current}}
{{^current}}
<li class="initialbarall page-item active">
<a class="page-link">{{all}}</a>
</li>
{{/current}}
</ul>
{{#group}}
<ul class="initialbargroup">
<ul class="pagination pagination-sm">
{{#letter}}
{{#selected}}
<li><span class="letter page-item active {{name}}">{{name}}</span></li>
<li class="page-item active {{name}}"><span class="page-link">{{name}}</span></li>
{{/selected}}
{{^selected}}
<li><a class="letter page-item {{name}}" href="{{url}}">{{name}}</a></li>
<li class="page-item {{name}}"><a class="page-link" href="{{url}}">{{name}}</a></li>
{{/selected}}
{{/letter}}
</ul>

View File

@ -19,149 +19,210 @@
Moodle template for the login page.
Context variables required for this template:
* autofocusform: Auto focus on form ?,
* canloginasguest - Is guest login allowed?,
* canloginbyemail - Is login by email allowed?,
* cansignup - Signup allowed?,
* cookieshelpicon - cookies help icon details
* error - Any errors in the form?,
* forgotpasswordurl - Forgot password url,
* hasidentityproviders - Flag, set to true to hide identity providers,
* hasinstructions - Flag, set to true to show instructions,
* identityproviders - List of identiy providers,
* instructions - Instructions,
* instructionsformat - Format of instructions,
* loginurl - Login url,
* rememberusername - Remeber username?,
* signupurl - Signup url,
* cookieshelpiconformatted - Formatted html of cookies help icon,
* errorformatted - Formatted error,
* logourl - Flag, logo url,
* sitename - Name of site.
* logintoken - Random token to protect login request.
Example context (json):
{
"autofocusform": false,
"canloginasguest": true,
"canloginbyemail": true,
"canloginasguest": "1",
"canloginbyemail": false,
"cansignup": true,
"error": "testerror",
"errorformatted": "Test error formatted",
"forgotpasswordurl": "http://example.com/login/forgot_password.php",
"cookieshelpicon": {
"heading": "Cookies must be enabled in your browser",
"text": "<div class=\"no-overflow\">Two cookies are used on this site. Both died..</div>",
"icon": {
"attributes": [
{
"name": "class",
"value": "iconhelp"
},
{
"name": "alt",
"value": "Help with Cookies must be enabled in your browser"
},
{
"name": "title",
"value": "Help with Cookies must be enabled in your browser"
},
{
"name": "src",
"value": "http://localhost/stable_master/theme/image.php?theme=boost&component=core&image=help"
}
]
},
"linktext": null,
"title": "Help with Cookies must be enabled in your browser",
"url": "http://localhost/stable_master/help.php?component=core&identifier=cookiesenabled&lang=en",
"ltr": true
},
"error": "",
"forgotpasswordurl": "http://localhost/stable_master/login/forgot_password.php",
"hasidentityproviders": false,
"hasinstructions": true,
"identityproviders": [],
"instructions": "For full access to this site, you first need to create an account.",
"loginurl": "http://example.com/stable_master/login/index.php",
"instructionsformat": "1",
"loginurl": "http://localhost/stable_master/login/index.php",
"rememberusername": true,
"signupurl": "http://localhost/stable_master/login/signup.php",
"cookieshelpiconformatted": "",
"username": "",
"errorformatted": "",
"logourl": false,
"sitename": "Beer & Chips",
"logintoken": "randomstring"
}
}}
{{#hasinstructions}}
<div class="loginbox clearfix twocolumns">
{{/hasinstructions}}
{{^hasinstructions}}
<div class="loginbox clearfix onecolumn">
{{/hasinstructions}}
<div class="loginpanel">
{{#cansignup}}
<div class="skiplinks">
<a class="skip" href="{{signupurl}}">{{#str}} tocreatenewaccount {{/str}}</a>
</div>
{{/cansignup}}
<h2>{{#str}} login {{/str}}</h2>
<div class="subcontent loginsub">
{{#error}}
<div class="loginerrors" role="alert">
<a href="#" id="loginerrormessage" class="accesshide">{{error}}</a>
{{{errorformatted}}}
</div>
{{/error}}
<form action="{{loginurl}}" method="post" id="login">
<div class="loginform">
<div class="form-label">
<label for="username">
{{^canloginbyemail}}
{{#str}} username {{/str}}
{{/canloginbyemail}}
{{#canloginbyemail}}
{{#str}} usernameemail {{/str}}
{{/canloginbyemail}}
</label>
</div>
<div class="form-input">
<input type="text" name="username" id="username" size="15" value="{{username}}" autocomplete="username">
</div>
<div class="clearer"><!-- --></div>
<div class="form-label">
<label for="password">{{#str}} password {{/str}}</label>
</div>
<div class="form-input">
<input type="password" name="password" id="password" size="15" value="" autocomplete="current-password">
</div>
</div>
<div class="clearer"><!-- --></div>
{{#rememberusername}}
<div class="rememberpass">
<input type="checkbox" name="rememberusername" id="rememberusername" value="1" {{#username}}checked="checked"{{/username}} />
<label for="rememberusername">{{#str}} rememberusername, admin {{/str}}</label>
</div>
{{/rememberusername}}
<div class="clearer"><!-- --></div>
<input id="anchor" type="hidden" name="anchor" value="" />
<script>document.getElementById('anchor').value = location.hash;</script>
<input type="hidden" name="logintoken" value="{{logintoken}}">
<input type="submit" id="loginbtn" value={{#quote}}{{#str}} login {{/str}}{{/quote}} />
<div class="forgetpass">
<a href="{{forgotpasswordurl}}">{{#str}} forgotten {{/str}}</a>
</div>
</form>
<div class="desc">
{{#str}} cookiesenabled {{/str}}
{{{cookieshelpiconformatted}}}
</div>
</div>
{{#canloginasguest}}
<div class="subcontent guestsub">
<div class="desc">{{#str}} someallowguest {{/str}}</div>
<form action="{{loginurl}}" method="post" id="guestlogin">
<div class="guestform">
<input type="hidden" name="logintoken" value="{{logintoken}}">
<input type="hidden" name="username" value="guest" />
<input type="hidden" name="password" value="guest" />
<input type="submit" value={{#quote}}{{#str}} loginguest {{/str}}{{/quote}} />
</div>
</form>
</div>
{{/canloginasguest}}
</div>
<div class="signuppanel">
{{#hasinstructions}}
<h2>{{#str}} firsttime {{/str}}</h2>
<div class="subcontent">
{{{instructions}}}
<div class="my-1 my-sm-5"></div>
<div class="row justify-content-center">
<div class="col-xl-6 col-sm-8 ">
<div class="card">
<div class="card-block">
{{#logourl}}
<h2 class="card-header text-center" ><img src="{{logourl}}" title="{{sitename}}" alt="{{sitename}}"/></h2>
{{/logourl}}
{{^logourl}}
<h2 class="card-header text-center">{{sitename}}</h2>
{{/logourl}}
<div class="card-body">
{{#cansignup}}
<div class="signupform">
<form action="{{signupurl}}" method="get" id="signup">
<div>
<input type="submit" value={{#quote}}{{#str}} startsignup {{/str}}{{/quote}} />
</div>
</form>
<div class="sr-only">
<a href="{{signupurl}}">{{#str}} tocreatenewaccount {{/str}}</a>
</div>
{{/cansignup}}
</div>
{{/hasinstructions}}
{{#hasidentityproviders}}
<div class="subcontent potentialidps">
<h6>{{#str}} potentialidps, auth {{/str}}</h6>
<div class="potentialidplist">
{{#identityproviders}}
<div class="potentialidp">
<a href="{{url}}" title={{#quote}}{{name}}{{/quote}} class="btn">
{{#iconurl}}
<img src="{{iconurl}}" alt="" width="24" height="24"/>
{{/iconurl}}
{{name}}
</a>
{{#error}}
<div class="loginerrors mt-3">
<a href="#" id="loginerrormessage" class="accesshide">{{error}}</a>
<div class="alert alert-danger" role="alert" data-aria-autofocus="true">{{error}}</div>
</div>
{{/error}}
<div class="row justify-content-md-center">
<div class="col-md-5">
<form class="mt-3" action="{{loginurl}}" method="post" id="login">
<input id="anchor" type="hidden" name="anchor" value="">
<script>document.getElementById('anchor').value = location.hash;</script>
<input type="hidden" name="logintoken" value="{{logintoken}}">
<div class="form-group">
<label for="username" class="sr-only">
{{^canloginbyemail}}
{{#str}} username {{/str}}
{{/canloginbyemail}}
{{#canloginbyemail}}
{{#str}} usernameemail {{/str}}
{{/canloginbyemail}}
</label>
<input type="text" name="username" id="username"
class="form-control"
value="{{username}}"
placeholder={{#quote}}{{^canloginbyemail}}{{#str}}username{{/str}}{{/canloginbyemail}}{{#canloginbyemail}}{{#str}}usernameemail{{/str}}{{/canloginbyemail}}{{/quote}}
autocomplete="username">
</div>
<div class="form-group">
<label for="password" class="sr-only">{{#str}} password {{/str}}</label>
<input type="password" name="password" id="password" value=""
class="form-control"
placeholder={{#quote}}{{#str}}password{{/str}}{{/quote}}
autocomplete="current-password">
</div>
{{#rememberusername}}
<div class="rememberpass mt-3">
<input type="checkbox" name="rememberusername" id="rememberusername" value="1" {{#username}}checked="checked"{{/username}} />
<label for="rememberusername">{{#str}} rememberusername, admin {{/str}}</label>
</div>
{{/rememberusername}}
<button type="submit" class="btn btn-primary btn-block mt-3" id="loginbtn">{{#str}}login{{/str}}</button>
</form>
</div>
<div class="col-md-5">
<div class="forgetpass mt-3">
<p><a href="{{forgotpasswordurl}}">{{#str}}forgotten{{/str}}</a></p>
</div>
{{/identityproviders}}
<div class="mt-3">
{{#str}} cookiesenabled {{/str}}
{{{cookieshelpiconformatted}}}
</div>
{{#canloginasguest}}
<div class="mt-2">
<p>{{#str}}someallowguest{{/str}}</p>
<form action="{{loginurl}}" method="post" id="guestlogin">
<input type="hidden" name="logintoken" value="{{logintoken}}">
<input type="hidden" name="username" value="guest" />
<input type="hidden" name="password" value="guest" />
<button class="btn btn-secondary btn-block" type="submit">{{#str}}loginguest{{/str}}</button>
</form>
</div>
{{/canloginasguest}}
{{#hasidentityproviders}}
<h6 class="mt-2">{{#str}} potentialidps, auth {{/str}}</h6>
<div class="potentialidplist" class="mt-3">
{{#identityproviders}}
<div class="potentialidp">
<a href="{{url}}" title={{#quote}}{{name}}{{/quote}} class="btn btn-secondary btn-block">
{{#iconurl}}
<img src="{{iconurl}}" alt="" width="24" height="24"/>
{{/iconurl}}
{{name}}
</a>
</div>
{{/identityproviders}}
</div>
{{/hasidentityproviders}}
</div>
</div>
</div>
{{/hasidentityproviders}}
</div>
</div>
</div>
</div>
{{#hasinstructions}}
<div class="row justify-content-center mt-3">
<div class="col-xl-6 col-sm-8">
<div class="card">
<div class="card-body">
<div class="card-title">
<h2>{{#str}}firsttime{{/str}}</h2>
</div>
<div>
{{{instructions}}}
{{#cansignup}}
<form class="mt-3" action="{{signupurl}}" method="get" id="signup">
<button type="submit" class="btn btn-secondary">{{#str}}startsignup{{/str}}</button>
</form>
{{/cansignup}}
</div>
</div>
</div>
</div>
</div>
{{/hasinstructions}}
{{#js}}
{{#error}}

View File

@ -40,28 +40,28 @@
}
}}
<div class="modal-container moodle-has-zindex hide" data-region="modal-container" aria-hidden="true" role="dialog">
<div class="modal {{$classes}}{{/classes}}"
data-region="modal"
aria-labelledby="{{uniqid}}-modal-title"
role="document" tabindex="0">
<div class="modal-header {{$headerclasses}}{{headerclasses}}{{/headerclasses}}" data-region="header">
<button type="button" class="close" data-action="hide" title="{{#str}} closebuttontitle {{/str}}"></button>
{{$header}}
<h3 id="{{uniqid}}-modal-title" class="modal-title" data-region="title">
{{$title}}{{title}}{{/title}}
</h3>
{{/header}}
</div>
<div class="modal-body" data-region="body">
{{$body}}
{{{body}}}
{{/body}}
</div>
<div class="modal-footer" data-region="footer">
{{$footer}}
{{{footer}}}
{{/footer}}
<div class="modal moodle-has-zindex" data-region="modal-container" aria-hidden="true" role="dialog">
<div class="modal-dialog {{$classes}}{{/classes}}" role="document" data-region="modal" aria-labelledby="{{uniqid}}-modal-title" tabindex="0">
<div class="modal-content">
<div class="modal-header {{$headerclasses}}{{headerclasses}}{{/headerclasses}}" data-region="header">
{{$header}}
<h5 id="{{uniqid}}-modal-title" class="modal-title" data-region="title">{{$title}}{{title}}{{/title}}</h5>
{{/header}}
<button type="button" class="close" data-action="hide" aria-label={{#quote}}{{#str}}closebuttontitle{{/str}}{{/quote}}>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" data-region="body">
{{$body}}
{{{body}}}
{{/body}}
</div>
<div class="modal-footer" data-region="footer">
{{$footer}}
{{{footer}}}
{{/footer}}
</div>
</div>
</div>
</div>

View File

@ -33,4 +33,4 @@
Example context (json):
{}
}}
<div class="modal-backdrop hide" aria-hidden="true" data-region="modal-backdrop"></div>
<div class="modal-backdrop in hide" aria-hidden="true" data-region="modal-backdrop"></div>

View File

@ -36,15 +36,9 @@
Example context (json):
{ "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar"}
}}
<div class="alert alert-error alert-block fade in {{ extraclasses }}" {{!
}}{{# announce }} role="alert"{{/ announce }}{{!
<div class="alert alert-danger alert-block fade in {{ extraclasses }}" {{!
}}{{# announce }} role="alert" data-aria-autofocus="true"{{/ announce }}{{!
}}>
{{# closebutton }}<button type="button" class="close" data-dismiss="alert">&times;</button>{{/ closebutton }}
{{{ message }}}
</div>
{{# js }}
require(['jquery', 'theme_bootstrapbase/bootstrap'], function($) {
// Setup closing of bootstrap alerts.
$().alert();
});
{{/ js }}

View File

@ -37,14 +37,9 @@
{ "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar"}
}}
<div class="alert alert-info alert-block fade in {{ extraclasses }}" {{!
}}{{# announce }} role="alert"{{/ announce }}{{!
}}{{# announce }} role="alert" data-aria-autofocus="true"{{/ announce }}{{!
}}>
{{# closebutton }}<button type="button" class="close" data-dismiss="alert">&times;</button>{{/ closebutton }}
{{{ message }}}
</div>
{{# js }}
require(['jquery', 'theme_bootstrapbase/bootstrap'], function($) {
// Setup closing of bootstrap alerts.
$().alert();
});
{{/ js }}

View File

@ -37,14 +37,8 @@
{ "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar"}
}}
<div class="alert alert-success alert-block fade in {{ extraclasses }}" {{!
}}{{# announce }} role="alert"{{/ announce }}{{!
}}{{# announce }} role="alert" data-aria-autofocus="true"{{/ announce }}{{!
}}>
{{# closebutton }}<button type="button" class="close" data-dismiss="alert">&times;</button>{{/ closebutton }}
{{{ message }}}
</div>
{{# js }}
require(['jquery', 'theme_bootstrapbase/bootstrap'], function($) {
// Setup closing of bootstrap alerts.
$().alert();
});
{{/ js }}

View File

@ -37,14 +37,8 @@
{ "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar"}
}}
<div class="alert alert-warning alert-block fade in {{ extraclasses }}" {{!
}}{{# announce }} role="alert"{{/ announce }}{{!
}}{{# announce }} role="alert" data-aria-autofocus="true"{{/ announce }}{{!
}}>
{{# closebutton }}<button type="button" class="close" data-dismiss="alert">&times;</button>{{/ closebutton }}
{{{ message }}}
</div>
{{# js }}
require(['jquery', 'theme_bootstrapbase/bootstrap'], function($) {
// Setup closing of bootstrap alerts.
$().alert();
});
{{/ js }}

View File

@ -25,12 +25,12 @@
"width": "500"
}
}}
<div class="progressbar_container" style="width: {{width}}px;" id="{{id}}">
<h2 id="{{id}}_status"></h2>
<div class="progress progress-striped active">
<div id="{{id}}_bar" class="bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">&nbsp;</div>
<div id="{{id}}" class="row progressbar_container">
<div class="col-md-6 push-md-3">
<p id="{{id}}_status" class="text-xs-center"></p>
<progress id="{{id}}_bar" class="progress progress-striped progress-animated" value="0" max="100"></progress>
<p id="{{id}}_estimate" class="text-xs-center"></p>
</div>
<p id="{{id}}_estimate"></p>
</div>
{{! We must not use the JS helper otherwise this gets executed too late. }}
@ -47,20 +47,18 @@
estimate = e.detail.estimate;
statusIndicator.textContent = msg;
progressBar.textContent = '' + percent + '%';
progressBar.setAttribute('value', Math.round(percent));
if (percent === 100) {
el.classList.add('progress-success');
estimateIndicator.textContent = '';
progressBar.classList.add('progress-success');
estimateIndicator.textContent = '100%';
} else {
if (estimate) {
estimateIndicator.textContent = estimate;
estimateIndicator.textContent = estimate + ' - ' + percent + '%';
} else {
estimateIndicator.textContent = '';
estimateIndicator.textContent = '' + percent + '%';
}
el.classList.remove('progress-success');
progressBar.classList.remove('progress-success');
}
progressBar.setAttribute('aria-valuenow', percent);
progressBar.setAttribute('style', 'width: ' + percent + '%');
});
})();
</script>

View File

@ -30,8 +30,8 @@
]
}
}}
<label for="{{id}}" class="accesshide">{{label}}</label>
<select name="{{name}}" id="{{id}}"{{#attributes}} {{name}}="{{value}}"{{/attributes}}>
<label for="{{id}}" class="sr-only">{{label}}</label>
<select name="{{name}}" id="{{id}}"{{#attributes}} {{name}}="{{value}}"{{/attributes}} class="form-control">
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/options}}

View File

@ -39,105 +39,107 @@
<ul class="nav nav-tabs" role="tablist">
<!-- First the top most node and immediate children -->
<li class="active"> <a href="#link{{node.key}}" data-toggle="tab" role="tab">{{node.text}}</a> </li>
<li class="nav-item">
<a class="nav-link active" href="#link{{node.key}}" data-toggle="tab" role="tab">{{node.text}}</a>
</li>
<!-- Now the first level children with sub nodes -->
{{#node.children}}
{{#children.count}}
{{#display}}
{{^is_short_branch}}
<li> <a href="#link{{key}}" data-toggle="tab" role="tab">{{text}}</a> </li>
<li class="nav-item">
<a class="nav-link" href="#link{{key}}" data-toggle="tab" role="tab">{{text}}</a>
</li>
{{/is_short_branch}}
{{/display}}
{{/children.count}}
{{/node.children}}
</ul>
<div class="tab-content">
<div class="tab-content mt-3">
<div class="tab-pane active" id="link{{node.key}}" role="tabpanel">
<div class="well">
<div class="container-fluid">
<div class="row">
<div class="span9 offset3">
<ul class="unstyled indented-list">
{{#node.children}}
{{^children.count}}
{{#display}}
<li><a href="{{{action}}}">{{text}}</a></li>
{{/display}}
{{/children.count}}
{{/node.children}}
</ul>
</div>
<div class="container">
<div class="row">
<div class="col-sm-3">
</div>
<div class="col">
<ul class="list-unstyled">
{{#node.children}}
{{^children.count}}
{{#display}}
<li><a href="{{{action}}}">{{text}}</a></li>
{{/display}}
{{/children.count}}
{{/node.children}}
</ul>
</div>
</div>
{{#node.children}}
{{#display}}
{{#children.count}}
{{#is_short_branch}}
{{#node.children}}
{{#display}}
{{#children.count}}
{{#is_short_branch}}
<hr>
<div class="row">
<div class="col-sm-3">
{{#action}}<h4><a href="{{action}}">{{text}}</a><h4>{{/action}}
{{^action}}<h4>{{text}}<h4>{{/action}}
</div>
<div class="col">
<ul class="list-unstyled">
{{#children}}
{{> core/settings_link_page_single }}
{{/children}}
</ul>
</div>
</div>
{{/is_short_branch}}
{{/children.count}}
{{/display}}
{{/node.children}}
</div>
</div>
{{#node.children}}
{{#children.count}}
<div class="tab-pane" id="link{{key}}" role="tabpanel">
<div class="container">
<div class="row">
<div class="col-sm-3">
{{#action}}<h4><a href="{{action}}">{{text}}</a><h4>{{/action}}
{{^action}}<h4>{{text}}<h4>{{/action}}
</div>
<div class="col-sm-9">
<ul class="list-unstyled">
{{#children}}
{{#display}}
{{^children.count}}
<li><a href="{{{action}}}">{{text}}</a></li>
{{/children.count}}
{{/display}}
{{/children}}
</ul>
</div>
</div>
{{#children}}
{{#display}}
{{#children.count}}
<hr>
<div class="row">
<div class="span3">
<div class="col-sm-3">
{{#action}}<h4><a href="{{action}}">{{text}}</a><h4>{{/action}}
{{^action}}<h4>{{text}}<h4>{{/action}}
</div>
<div class="span9">
<ul class="unstyled indented-list">
<div class="col-sm-9">
<ul class="list-unstyled">
{{#children}}
{{> core/settings_link_page_single }}
{{/children}}
</ul>
</div>
</div>
{{/is_short_branch}}
{{/children.count}}
{{/display}}
{{/node.children}}
</div>
</div>
</div>
{{#node.children}}
{{#children.count}}
<div class="tab-pane" id="link{{key}}" role="tabpanel">
<div class="well">
<div class="container-fluid">
<div class="row">
<div class="span3">
{{#action}}<h4><a href="{{action}}">{{text}}</a><h4>{{/action}}
{{^action}}<h4>{{text}}<h4>{{/action}}
</div>
<div class="span9">
<ul class="unstyled">
{{#children}}
{{#display}}
{{^children.count}}
<li><a href="{{{action}}}">{{text}}</a></li>
{{/children.count}}
{{/display}}
{{/children}}
</ul>
</div>
</div>
{{#children}}
{{#display}}
{{#children.count}}
<hr>
<div class="row">
<div class="span3">
{{#action}}<h4><a href="{{action}}">{{text}}</a><h4>{{/action}}
{{^action}}<h4>{{text}}<h4>{{/action}}
</div>
<div class="span9">
<ul class="unstyled indented-list">
{{#children}}
{{> core/settings_link_page_single }}
{{/children}}
</ul>
</div>
</div>
{{/children.count}}
{{/display}}
{{/children}}
</div>
{{/children.count}}
{{/display}}
{{/children}}
</div>
</div>
{{/children.count}}

Some files were not shown because too many files have changed in this diff Show More