Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0a0458154 | |||
| 58d1571e08 | |||
| 75283557ad | |||
| 7d23d3ae3a | |||
| af2c368089 | |||
| 3b7f2aa473 | |||
| 00bef0f91d | |||
| 26e53e8cc2 | |||
| be0dea2892 | |||
| e522e7fc67 | |||
| 7e19a2a640 | |||
| 724275abbc | |||
| ea79441d28 | |||
| ca580b25b9 | |||
| 2f785afb00 | |||
| 7b6fddd811 | |||
| ac77c1c217 | |||
| 71bbfc2495 | |||
| c56c08e9ce | |||
| 4c3df551a4 | |||
| 516b81954a | |||
| 4caed80b37 | |||
| 7f6e49af41 | |||
| 69cca28118 | |||
| 3e30033749 | |||
| d61b9111fa | |||
| 07a30d6650 | |||
| 6f25549278 | |||
| 5ee4098ce5 | |||
| 44956b2463 | |||
| e65075c421 | |||
| 3d95f94ca6 | |||
| 1b51d716e2 | |||
| 7602fa7ad9 | |||
| f2ef4c6808 | |||
| 757f0a56c9 | |||
| e22f3d47c2 | |||
| 1ef498d989 | |||
| 747fff61c4 | |||
| 0f368a42cc | |||
| 186284ec1f | |||
| cdfdfc30d9 | |||
| 6c95086bc9 | |||
| 45ef55471b | |||
| ac270cb26f | |||
| b2ba6624ce | |||
| b57eb5dd88 | |||
| 9bf42e7aad | |||
| 4318e21563 | |||
| 2a900d6ed9 | |||
| 385721f105 | |||
| 83c5af5001 | |||
| 1b995b811d | |||
| 52ad957d74 | |||
| edb68975d1 | |||
| 9d84286479 | |||
| 7b8be76dd9 | |||
| c2bcdedbd6 | |||
| ca40241461 | |||
| 5e7ec29c65 | |||
| 193e6b0fad | |||
| b7a8375e65 | |||
| 10e93d074b | |||
| 0e6246db29 | |||
| 58efa0fa10 | |||
| afc3bfc296 | |||
| 9af8958b03 | |||
| 896da8f473 | |||
| 90fa1113f2 | |||
| aaf985001e | |||
| 9b81be597c | |||
| 998f6ce060 | |||
| 881ad82c44 | |||
| 5c163fff89 | |||
| 1ac64c87f9 | |||
| ce35013ebf | |||
| 8de6b2d6f4 | |||
| 81fc25e7fa | |||
| 5efe1cbf3c | |||
| 5deff67cea | |||
| 14cb950ff3 | |||
| 374028b418 | |||
| f0133eaae1 | |||
| 18998a6051 | |||
| 00b94dec34 | |||
| 71269d9c8f | |||
| 61c44c40d8 | |||
| 4629c45815 |
@@ -65,6 +65,7 @@ jobs:
|
||||
extensions:
|
||||
db: mysqli
|
||||
- os: ubuntu-22.04
|
||||
extensions: mongodb-1.19.4
|
||||
php: 8.1
|
||||
db: pgsql
|
||||
|
||||
|
||||
@@ -4895,4 +4895,201 @@
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="5.1" requires="4.2.3">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="10.11.0" />
|
||||
<VENDOR name="mysql" version="8.4" />
|
||||
<VENDOR name="postgres" version="14" />
|
||||
<VENDOR name="mssql" version="14.0" />
|
||||
</DATABASE>
|
||||
<PHP version="8.2.0" level="required">
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="pcreunicodewarning" />
|
||||
</FEEDBACK>
|
||||
</PCREUNICODE>
|
||||
<PHP_EXTENSIONS>
|
||||
<PHP_EXTENSION name="iconv" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="iconvrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="mbstring" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="mbstringrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="curl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="curlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="openssl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="opensslrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="tokenizer" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="tokenizerrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="soap" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="soaprecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="ctype" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ctyperequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zip" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ziprequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zlib" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="gd" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="gdrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="simplexml" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="simplexmlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="spl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="splrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="pcre" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="dom" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xml" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlreader" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="intl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="intlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="json" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
<PHP_EXTENSION name="fileinfo" level="required"/>
|
||||
<PHP_EXTENSION name="sodium" level="required"/>
|
||||
<PHP_EXTENSION name="exif" level="optional"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="settingmemorylimit" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="file_uploads" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingfileuploads" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="opcache.enable" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opcacherecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="zend.exception_ignore_args" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingzendexceptionignoreargs" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
</PHP_SETTINGS>
|
||||
<CUSTOM_CHECKS>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbstorageengine" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="quizattemptsupgradedmessage" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="slashargumentswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unsupporteddbtablerowformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unoconvwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="libcurlwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfileformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfilepertable" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddblargeprefix" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="ishttpswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="incompleteunicodesupport" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="sixtyfourbitsrequired" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_admin_dir_usage" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="iscustomadminwarnings" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mod_assignment" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
</COMPATIBILITY_MATRIX>
|
||||
|
||||
@@ -82,6 +82,8 @@ class behat_admin extends behat_base {
|
||||
|
||||
$this->execute('behat_forms::i_set_the_field_with_xpath_to', [$fieldxpath, $value]);
|
||||
$this->execute("behat_general::i_click_on", [get_string('savechanges'), 'button']);
|
||||
// Wait for the page to be redirected.
|
||||
$this->execute("behat_general::i_wait_to_be_redirected");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ if (function_exists('opcache_reset')) {
|
||||
define('CLI_SCRIPT', true);
|
||||
define('CACHE_DISABLE_ALL', true);
|
||||
|
||||
// It makes no sense to use BEHAT_CLI for this script (the Behat launch scripts expect to start
|
||||
// from the normal environment), so in case user has set tne environment variable, disable it.
|
||||
putenv('BEHAT_CLI=0');
|
||||
|
||||
// Basic functions.
|
||||
require_once(__DIR__ . '/../../../../lib/clilib.php');
|
||||
require_once(__DIR__ . '/../../../../lib/behat/lib.php');
|
||||
|
||||
@@ -31,6 +31,10 @@ define('ABORT_AFTER_CONFIG', true);
|
||||
define('CACHE_DISABLE_ALL', true);
|
||||
define('NO_OUTPUT_BUFFERING', true);
|
||||
|
||||
// It makes no sense to use BEHAT_CLI for this script (the Behat launch scripts expect to start
|
||||
// from the normal environment), so in case user has set tne environment variable, disable it.
|
||||
putenv('BEHAT_CLI=0');
|
||||
|
||||
require_once(__DIR__ .'/../../../../config.php');
|
||||
require_once(__DIR__.'/../../../../lib/clilib.php');
|
||||
require_once(__DIR__.'/../../../../lib/behat/lib.php');
|
||||
|
||||
@@ -39,6 +39,10 @@ define('NO_OUTPUT_BUFFERING', true);
|
||||
define('IGNORE_COMPONENT_CACHE', true);
|
||||
define('ABORT_AFTER_CONFIG', true);
|
||||
|
||||
// It makes no sense to use BEHAT_CLI for this script (the Behat launch scripts expect to start
|
||||
// from the normal environment), so in case user has set tne environment variable, disable it.
|
||||
putenv('BEHAT_CLI=0');
|
||||
|
||||
require_once(__DIR__ . '/../../../../lib/clilib.php');
|
||||
|
||||
// CLI options.
|
||||
|
||||
@@ -33,6 +33,10 @@ if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||
die(); // No access from web!.
|
||||
}
|
||||
|
||||
// It makes no sense to use BEHAT_CLI for this script (the Behat launch scripts expect to start
|
||||
// from the normal environment), so in case user has set tne environment variable, disable it.
|
||||
putenv('BEHAT_CLI=0');
|
||||
|
||||
// Basic functions.
|
||||
require_once(__DIR__ . '/../../../../lib/clilib.php');
|
||||
require_once(__DIR__ . '/../../../../lib/behat/lib.php');
|
||||
|
||||
@@ -87,6 +87,7 @@ $action = optional_param('action', '', PARAM_ALPHA);
|
||||
// Handle any single operation actions.
|
||||
if ($action == 'requestanalysis') {
|
||||
if ($courseid != 0) {
|
||||
require_sesskey();
|
||||
scheduler::request_course_analysis($courseid);
|
||||
if ($courseid == SITEID) {
|
||||
redirect(accessibility::get_plugin_url());
|
||||
|
||||
@@ -46,11 +46,11 @@ Feature: See the competencies for an activity on the course competencies page.
|
||||
When I follow "Competencies"
|
||||
Then I should see "Test-Comp1"
|
||||
And I should see "Test-Comp2"
|
||||
And I set the field "Filter competencies by resource or activity" to "PageName1"
|
||||
And I set the competency filter "Filter competencies by resource or activity" to "PageName1"
|
||||
And I press the enter key
|
||||
And I should see "Test-Comp1"
|
||||
And I should not see "Test-Comp2"
|
||||
And I set the field "Filter competencies by resource or activity" to "PageName2"
|
||||
And I set the competency filter "Filter competencies by resource or activity" to "PageName2"
|
||||
And I press the enter key
|
||||
And I should not see "Test-Comp1"
|
||||
And I should not see "Test-Comp2"
|
||||
|
||||
@@ -34,6 +34,10 @@ if (function_exists('opcache_reset')) {
|
||||
|
||||
define('IGNORE_COMPONENT_CACHE', true);
|
||||
|
||||
// It makes no sense to use BEHAT_CLI for this script (you cannot initialise PHPunit starting from
|
||||
// the Behat environment), so in case user has set tne environment variable, disable it.
|
||||
putenv('BEHAT_CLI=0');
|
||||
|
||||
require_once(__DIR__.'/../../../../lib/clilib.php');
|
||||
require_once(__DIR__.'/../../../../lib/phpunit/bootstraplib.php');
|
||||
require_once(__DIR__.'/../../../../lib/testing/lib.php');
|
||||
|
||||
@@ -30,6 +30,10 @@ if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||
|
||||
define('IGNORE_COMPONENT_CACHE', true);
|
||||
|
||||
// It makes no sense to use BEHAT_CLI for this script (you cannot initialise PHPunit starting from
|
||||
// the Behat environment), so in case user has set tne environment variable, disable it.
|
||||
putenv('BEHAT_CLI=0');
|
||||
|
||||
require_once(__DIR__.'/../../../../lib/clilib.php');
|
||||
require_once(__DIR__.'/../../../../lib/phpunit/bootstraplib.php');
|
||||
require_once(__DIR__.'/../../../../lib/testing/lib.php');
|
||||
|
||||
@@ -218,12 +218,11 @@ class helper {
|
||||
* @return moodle_url The URL.
|
||||
*/
|
||||
public static function get_duplicate_tour_link($tourid) {
|
||||
$link = new \moodle_url('/admin/tool/usertours/configure.php', [
|
||||
return new \moodle_url('/admin/tool/usertours/configure.php', [
|
||||
'action' => manager::ACTION_DUPLICATETOUR,
|
||||
'id' => $tourid,
|
||||
'sesskey' => sesskey(),
|
||||
]);
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -512,8 +512,9 @@ class manager {
|
||||
* @param int $tourid The ID of the tour to duplicate.
|
||||
*/
|
||||
protected function duplicate_tour($tourid) {
|
||||
$tour = helper::get_tour($tourid);
|
||||
require_sesskey();
|
||||
|
||||
$tour = helper::get_tour($tourid);
|
||||
$export = $tour->to_record();
|
||||
// Remove the id.
|
||||
unset($export->id);
|
||||
|
||||
@@ -168,8 +168,7 @@ abstract class restore_qtype_plugin extends restore_plugin {
|
||||
$this->questionanswercacheid = $newquestionid;
|
||||
// Cache all cleaned answers for a simple text match.
|
||||
foreach ($answers as $answer) {
|
||||
// MDL-30018: Clean in the same way as {@link xml_writer::xml_safe_utf8()}.
|
||||
$clean = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is','', $answer->answer); // Clean CTRL chars.
|
||||
$clean = core_text::trim_ctrl_chars($answer->answer); // Clean CTRL chars.
|
||||
$clean = preg_replace("/\r\n|\r/", "\n", $clean); // Normalize line ending.
|
||||
$this->questionanswercache[$clean] = $answer->id;
|
||||
}
|
||||
|
||||
@@ -5154,8 +5154,7 @@ class restore_create_categories_and_questions extends restore_structure_step {
|
||||
$potentialhints = $DB->get_records('question_hints',
|
||||
array('questionid' => $newquestionid), '', 'id, hint');
|
||||
foreach ($potentialhints as $potentialhint) {
|
||||
// Clean in the same way than {@link xml_writer::xml_safe_utf8()}.
|
||||
$cleanhint = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is','', $potentialhint->hint); // Clean CTRL chars.
|
||||
$cleanhint = core_text::trim_ctrl_chars($potentialhint->hint); // Clean CTRL chars.
|
||||
$cleanhint = preg_replace("/\r\n|\r/", "\n", $cleanhint); // Normalize line ending.
|
||||
if ($cleanhint === $data->hint) {
|
||||
$newitemid = $data->id;
|
||||
|
||||
@@ -253,14 +253,14 @@ class xml_writer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform some UTF-8 cleaning, stripping the control chars (\x0-\x1f)
|
||||
* but tabs (\x9), newlines (\xa) and returns (\xd). The delete control
|
||||
* Perform some UTF-8 cleaning, stripping the control chars (\x00-\x1f)
|
||||
* but tabs (\x09), newlines (\xa) and returns (\xd). The delete control
|
||||
* char (\x7f) is also included. All them are forbiden in XML 1.0 specs.
|
||||
* The expression below seems to be UTF-8 safe too because it simply
|
||||
* ignores the rest of characters. Also normalize linefeeds and return chars.
|
||||
*/
|
||||
protected function xml_safe_utf8($content) {
|
||||
$content = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is', '', $content ?? ''); // clean CTRL chars.
|
||||
$content = core_text::trim_ctrl_chars($content ?? '');
|
||||
$content = preg_replace("/\r\n|\r/", "\n", $content); // Normalize line&return=>line
|
||||
return fix_utf8($content);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ if ($action == 'delete' && $confirm && confirm_sesskey()) {
|
||||
} else {
|
||||
$msg = get_string('sitebackpacknotdeleted', 'badges');
|
||||
}
|
||||
} else if ($action == 'moveup' || $action == 'movedown') {
|
||||
} else if (($action == 'moveup' || $action == 'movedown') && confirm_sesskey()) {
|
||||
// If no backpack has been selected, there isn't anything to move.
|
||||
if (empty($id)) {
|
||||
redirect($url);
|
||||
|
||||
@@ -56,13 +56,13 @@
|
||||
<td> {{{backpackweburl}}} </td>
|
||||
<td>
|
||||
{{#canmoveup}}
|
||||
<a href="{{baseurl}}?id={{id}}&action=moveup">{{#pix}}t/up, core,{{#str}}moveup{{/str}}{{/pix}}</a>
|
||||
<a href="{{baseurl}}?id={{id}}&sesskey={{sesskey}}&action=moveup">{{#pix}}t/up, core,{{#str}}moveup{{/str}}{{/pix}}</a>
|
||||
{{/canmoveup}}
|
||||
{{^canmoveup}}
|
||||
{{#pix}}spacer, moodle{{/pix}}
|
||||
{{/canmoveup}}
|
||||
{{#canmovedown}}
|
||||
<a href="{{baseurl}}?id={{id}}&action=movedown">{{#pix}}t/down, core,{{#str}}movedown{{/str}}{{/pix}}</a>
|
||||
<a href="{{baseurl}}?id={{id}}&sesskey={{sesskey}}&action=movedown">{{#pix}}t/down, core,{{#str}}movedown{{/str}}{{/pix}}</a>
|
||||
{{/canmovedown}}
|
||||
{{^canmovedown}}
|
||||
{{#pix}}spacer, moodle{{/pix}}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Script to let a user edit the properties of a particular RSS feed.
|
||||
* Script to let a user view the output of a particular RSS feed.
|
||||
*
|
||||
* @package block_rss_client
|
||||
* @copyright 2009 Tim Hunt
|
||||
@@ -26,9 +26,6 @@ require_once(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->libdir .'/simplepie/moodle_simplepie.php');
|
||||
|
||||
require_login();
|
||||
if (isguestuser()) {
|
||||
throw new \moodle_exception('guestsarenotallowed');
|
||||
}
|
||||
|
||||
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
|
||||
$courseid = optional_param('courseid', 0, PARAM_INT);
|
||||
@@ -46,6 +43,11 @@ if ($courseid) {
|
||||
$PAGE->set_context($context);
|
||||
}
|
||||
|
||||
$managesharedfeeds = has_capability('block/rss_client:manageanyfeeds', $context);
|
||||
if (!$managesharedfeeds) {
|
||||
require_capability('block/rss_client:manageownfeeds', $context);
|
||||
}
|
||||
|
||||
$urlparams = array('rssid' => $rssid);
|
||||
if ($courseid) {
|
||||
$urlparams['courseid'] = $courseid;
|
||||
@@ -56,10 +58,18 @@ if ($returnurl) {
|
||||
$PAGE->set_url('/blocks/rss_client/viewfeed.php', $urlparams);
|
||||
$PAGE->set_pagelayout('popup');
|
||||
|
||||
$rssrecord = $DB->get_record('block_rss_client', array('id' => $rssid), '*', MUST_EXIST);
|
||||
if ($managesharedfeeds) {
|
||||
$select = 'id = :id AND (userid = :userid OR shared = 1)';
|
||||
} else {
|
||||
$select = 'id = :id AND userid = :userid';
|
||||
}
|
||||
|
||||
$rssrecord = $DB->get_record_select('block_rss_client', $select, [
|
||||
'id' => $rssid,
|
||||
'userid' => $USER->id,
|
||||
], '*', MUST_EXIST);
|
||||
|
||||
$rss = new moodle_simplepie($rssrecord->url);
|
||||
|
||||
if ($rss->error()) {
|
||||
debugging($rss->error());
|
||||
throw new \moodle_exception('errorfetchingrssfeed');
|
||||
|
||||
@@ -55,11 +55,11 @@ class cohorts extends system_report {
|
||||
"{$entitymainalias}.component");
|
||||
|
||||
// Check if report needs to show a specific category.
|
||||
$contextid = $this->get_parameter('contextid', 0, PARAM_INT);
|
||||
$showall = $this->get_parameter('showall', true, PARAM_BOOL);
|
||||
if (!$showall) {
|
||||
if (!$this->get_context() instanceof context_system || !$this->get_parameter('showall', false, PARAM_BOOL)) {
|
||||
$paramcontextid = database::generate_param_name();
|
||||
$this->add_base_condition_sql("{$entitymainalias}.contextid = :$paramcontextid", [$paramcontextid => $contextid]);
|
||||
$this->add_base_condition_sql("{$entitymainalias}.contextid = :{$paramcontextid}", [
|
||||
$paramcontextid => $this->get_context()->id,
|
||||
]);
|
||||
}
|
||||
|
||||
// Now we can call our helper methods to add the content we want to include in the report.
|
||||
@@ -77,14 +77,7 @@ class cohorts extends system_report {
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_view(): bool {
|
||||
$contextid = $this->get_parameter('contextid', 0, PARAM_INT);
|
||||
if ($contextid) {
|
||||
$context = context::instance_by_id($contextid, MUST_EXIST);
|
||||
} else {
|
||||
$context = context_system::instance();
|
||||
}
|
||||
|
||||
return has_any_capability(['moodle/cohort:manage', 'moodle/cohort:view'], $context);
|
||||
return has_any_capability(['moodle/cohort:manage', 'moodle/cohort:view'], $this->get_context());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,10 +91,8 @@ class cohorts extends system_report {
|
||||
public function add_columns(cohort $cohortentity): void {
|
||||
|
||||
$entitymainalias = $cohortentity->get_table_alias('cohort');
|
||||
$showall = $this->get_parameter('showall', false, PARAM_BOOL);
|
||||
|
||||
// Category column. An extra callback is appended in order to extend the current column formatting.
|
||||
if ($showall) {
|
||||
if ($this->get_context() instanceof context_system && $this->get_parameter('showall', false, PARAM_BOOL)) {
|
||||
$this->add_column_from_entity('cohort:context')
|
||||
->add_callback(static function(string $value, stdClass $cohort): string {
|
||||
$context = context::instance_by_id($cohort->contextid);
|
||||
@@ -195,10 +186,11 @@ class cohorts extends system_report {
|
||||
*/
|
||||
protected function add_actions(): void {
|
||||
|
||||
$contextid = $this->get_parameter('contextid', 0, PARAM_INT);
|
||||
$showall = $this->get_parameter('showall', true, PARAM_BOOL);
|
||||
$returnurl = (new moodle_url('/cohort/index.php',
|
||||
['id' => ':id', 'contextid' => $contextid, 'showall' => $showall]))->out(false);
|
||||
$returnurl = (new moodle_url('/cohort/index.php', [
|
||||
'id' => ':id',
|
||||
'contextid' => $this->get_context()->id,
|
||||
'showall' => $this->get_parameter('showall', false, PARAM_BOOL),
|
||||
]))->out(false);
|
||||
|
||||
// Hide action. It will be only shown if the property 'visible' is true and user has 'moodle/cohort:manage' capabillity.
|
||||
$this->add_action((new action(
|
||||
|
||||
+1
-2
@@ -104,8 +104,7 @@ if ($editcontrols = cohort_edit_controls($context, $baseurl)) {
|
||||
echo $OUTPUT->render($editcontrols);
|
||||
}
|
||||
|
||||
$reportparams = ['contextid' => $context->id, 'showall' => $showall];
|
||||
$report = system_report_factory::create(cohorts::class, $context, '', '', 0, $reportparams);
|
||||
$report = system_report_factory::create(cohorts::class, $context, '', '', 0, ['showall' => $showall]);
|
||||
|
||||
// Check if it needs to search by name.
|
||||
if (!empty($searchquery)) {
|
||||
|
||||
+13
-7
@@ -4127,7 +4127,7 @@ class core_course_external extends external_api {
|
||||
|
||||
self::validate_context($usercontext);
|
||||
|
||||
if ($userid != $USER->id and !has_capability('moodle/user:viewdetails', $usercontext)) {
|
||||
if ($userid != $USER->id && !has_capability('moodle/user:viewalldetails', $usercontext)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
@@ -4177,22 +4177,28 @@ class core_course_external extends external_api {
|
||||
* @param int $groupid Group id from which the users will be obtained
|
||||
* @param bool $onlyactive Whether to return only the active enrolled users or all enrolled users in the course.
|
||||
* @return array List of users
|
||||
* @throws invalid_parameter_exception
|
||||
*/
|
||||
public static function get_enrolled_users_by_cmid(int $cmid, int $groupid = 0, bool $onlyactive = false) {
|
||||
global $PAGE;
|
||||
global $PAGE;
|
||||
|
||||
$warnings = [];
|
||||
|
||||
self::validate_parameters(self::get_enrolled_users_by_cmid_parameters(), [
|
||||
'cmid' => $cmid,
|
||||
'groupid' => $groupid,
|
||||
'onlyactive' => $onlyactive,
|
||||
[
|
||||
'cmid' => $cmid,
|
||||
'groupid' => $groupid,
|
||||
'onlyactive' => $onlyactive,
|
||||
] = self::validate_parameters(self::get_enrolled_users_by_cmid_parameters(), [
|
||||
'cmid' => $cmid,
|
||||
'groupid' => $groupid,
|
||||
'onlyactive' => $onlyactive,
|
||||
]);
|
||||
|
||||
list($course, $cm) = get_course_and_cm_from_cmid($cmid);
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
self::validate_context($coursecontext);
|
||||
|
||||
course_require_view_participants($coursecontext);
|
||||
|
||||
$enrolledusers = get_enrolled_users($coursecontext, '', $groupid, 'u.*', null, 0, 0, $onlyactive);
|
||||
|
||||
$users = array_map(function ($user) use ($PAGE) {
|
||||
|
||||
@@ -242,6 +242,9 @@ class stateactions {
|
||||
|
||||
foreach ($ids as $sectionid) {
|
||||
$section = $modinfo->get_section_info_by_id($sectionid, MUST_EXIST);
|
||||
if (!course_can_delete_section($course, $section)) {
|
||||
continue;
|
||||
}
|
||||
// Send all activity deletions.
|
||||
if (!empty($modinfo->sections[$section->section])) {
|
||||
foreach ($modinfo->sections[$section->section] as $modnumber) {
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_courseformat\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_courseformat\stateactions;
|
||||
use core_courseformat\stateupdates;
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
|
||||
|
||||
/**
|
||||
* Tests for the delete section test class.
|
||||
*
|
||||
* @package core_courseformat
|
||||
* @copyright 2025 Laurent David <laurent.david@moodle.com>
|
||||
* @category test
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \core_courseformat\stateactions
|
||||
*/
|
||||
final class delete_section_test extends \externallib_advanced_testcase {
|
||||
|
||||
/**
|
||||
* Setup to ensure that fixtures are loaded.
|
||||
*/
|
||||
public static function setupBeforeClass(): void { // phpcs:ignore
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittestdelete.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the webservice can execute the section_delete action.
|
||||
*
|
||||
* @covers ::section_delete
|
||||
* @dataProvider section_delete_provider
|
||||
* @param int $sectionum
|
||||
* @param string $format
|
||||
* @param array $formatoptions
|
||||
* @param int $expectedsectionum
|
||||
*
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public function test_delete_section(int $sectionum, string $format, array $formatoptions, int $expectedsectionum): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course =
|
||||
$this->getDataGenerator()->create_course(array_merge(
|
||||
['numsections' => $sectionum, 'format' => $format],
|
||||
$formatoptions,
|
||||
));
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
// Execute the method.
|
||||
$courseformat = course_get_format($course->id);
|
||||
$updates = new stateupdates($courseformat);
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$sections = $modinfo->get_section_info_all();
|
||||
$sectionsid = array_map(function ($section) {
|
||||
return $section->id;
|
||||
}, $sections);
|
||||
$actions = new stateactions();
|
||||
$this->setUser($teacher);
|
||||
$actions->section_delete(
|
||||
$updates,
|
||||
$course,
|
||||
$sectionsid
|
||||
);
|
||||
// Check result.
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$sections = $modinfo->get_section_info_all();
|
||||
$this->assertCount($expectedsectionum, $sections);
|
||||
if ($format == 'theunittestdelete') {
|
||||
$this->assertDebuggingCalled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the test_delete_section method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function section_delete_provider(): array {
|
||||
return [
|
||||
'format topic' => [
|
||||
'sectionum' => 1,
|
||||
'format' => 'topics',
|
||||
'formatoptions' => [],
|
||||
'expectedsectionum' => 1,
|
||||
],
|
||||
'format theunittestdelete' => [
|
||||
'sectionum' => 1,
|
||||
'format' => 'theunittestdelete',
|
||||
'formatoptions' => [],
|
||||
'expectedsectionum' => 2,
|
||||
],
|
||||
'format theunittestdelete can delete' => [
|
||||
'sectionum' => 1,
|
||||
'format' => 'theunittestdelete',
|
||||
'formatoptions' => ['can_delete_sections' => true],
|
||||
'expectedsectionum' => 1,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once(__DIR__ . '/format_theunittest.php');
|
||||
|
||||
/**
|
||||
* Fixture for fake course format testing course format API.
|
||||
*
|
||||
* @package core_courseformat
|
||||
* @copyright 2025 Laurent David <laurent.david@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class format_theunittestdelete extends format_theunittest {
|
||||
|
||||
/**
|
||||
* Definitions of the additional options that format uses
|
||||
*
|
||||
* @param bool $foreditform
|
||||
* @return array of options
|
||||
*/
|
||||
public function course_format_options($foreditform = false) {
|
||||
static $courseformatoptions = false;
|
||||
if ($courseformatoptions === false) {
|
||||
$courseformatoptions = parent::course_format_options(true);
|
||||
$courseformatoptionsadditional = [
|
||||
'can_delete_sections' => [
|
||||
'default' => false,
|
||||
'type' => PARAM_BOOL,
|
||||
],
|
||||
];
|
||||
$courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsadditional);
|
||||
}
|
||||
return $courseformatoptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this format allows to delete sections
|
||||
*
|
||||
* Here for test purpose we just can delete one section every two sections
|
||||
*
|
||||
* Do not call this function directly, instead use course_can_delete_section()
|
||||
*
|
||||
* @param int|stdClass|section_info $section
|
||||
* @return bool
|
||||
*/
|
||||
public function can_delete_section($section) {
|
||||
return $this->get_format_options()['can_delete_sections'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this course format uses sections
|
||||
*/
|
||||
public function uses_sections() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -1757,7 +1757,14 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
// This is a request for the course information.
|
||||
$courseid = required_param('courseid', PARAM_INT);
|
||||
|
||||
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
||||
$course = $DB->get_record('course', ['id' => $courseid], '*', IGNORE_MISSING);
|
||||
if ($course === false) {
|
||||
throw new \moodle_exception('invalidcourseid');
|
||||
}
|
||||
$coursecontext = context_course::instance($course->id, MUST_EXIST);
|
||||
if ($course->visible == 0 && !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
|
||||
throw new \moodle_exception('invalidcourseid');
|
||||
}
|
||||
|
||||
$chelper = new coursecat_helper();
|
||||
$chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED);
|
||||
|
||||
@@ -3639,6 +3639,12 @@ final class externallib_test extends externallib_advanced_testcase {
|
||||
$this->assignUserCapability('moodle/user:viewdetails', $usercontext, $teacherroleid);
|
||||
|
||||
// Sorted by course id DESC.
|
||||
// User without moodle/user:viewalldetails capability will not be able to see the course details.
|
||||
$result = core_course_external::get_recent_courses($student->id);
|
||||
$this->assertCount(0, $result);
|
||||
|
||||
// User with moodle/user:viewalldetails capability will be able to see the course details.
|
||||
$this->assignUserCapability('moodle/user:viewalldetails', $usercontext, $teacherroleid);
|
||||
$result = core_course_external::get_recent_courses($student->id);
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertEquals($courses[0]->id, array_shift($result)->id);
|
||||
@@ -3742,6 +3748,12 @@ final class externallib_test extends externallib_advanced_testcase {
|
||||
|
||||
$this->assertEquals(2, count($users['users']));
|
||||
$this->assertEquals($expectedusers, $users);
|
||||
|
||||
// Prohibit the capability for viewing course participants.
|
||||
$this->unassignUserCapability('moodle/course:viewparticipants', null, null, $course1->id);
|
||||
$this->expectException(required_capability_exception::class);
|
||||
$this->expectExceptionMessage('Sorry, but you do not currently have permissions to do that (View participants)');
|
||||
core_course_external::get_enrolled_users_by_cmid($forum1->cmid);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+10
-1
@@ -84,11 +84,20 @@ function filter_tex_sanitize_formula(string $texexp): string {
|
||||
'\afterassignment', '\expandafter', '\noexpand', '\special',
|
||||
'\let', '\futurelet', '\else', '\fi', '\chardef', '\makeatletter', '\afterground',
|
||||
'\noexpand', '\line', '\mathcode', '\item', '\section', '\mbox', '\declarerobustcommand',
|
||||
'\ExplSyntaxOn', '\pdffiledump',
|
||||
'\ExplSyntaxOn', '\pdffiledump', '\mathtex',
|
||||
];
|
||||
|
||||
$allowlist = ['inputenc'];
|
||||
|
||||
// Add encoded backslash (\) versions of backslashed items to deny list.
|
||||
$encodedslashdenylist = array_map(function($value) {
|
||||
$encoded = str_replace('\\', '\', $value);
|
||||
// Return an encoded slash version if a slash is found, otherwise null so we can filter it off.
|
||||
return $encoded != $value ? $encoded : null;
|
||||
}, $denylist);
|
||||
$encodedslashdenylist = array_filter($encodedslashdenylist);
|
||||
$denylist = array_merge($denylist, $encodedslashdenylist);
|
||||
|
||||
// Prepare the denylist for regular expression.
|
||||
$denylist = array_map(function($value){
|
||||
return '/' . preg_quote($value, '/') . '/i';
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['language'] = 'Idioma';
|
||||
$string['moodlelogo'] = 'Logo de Moodle';
|
||||
$string['next'] = 'Següent';
|
||||
$string['previous'] = 'Anterior';
|
||||
$string['reload'] = 'Torna a carregar';
|
||||
@@ -71,7 +71,7 @@ $string['pathserrcreatedataroot'] = 'Instalatzaileak ezin du datu-direktorioa ({
|
||||
$string['pathshead'] = 'Egiaztatu bideak';
|
||||
$string['pathsrodataroot'] = 'Dataroot direktorioa ez da idazteko modukoa.';
|
||||
$string['pathsroparentdataroot'] = 'Goragoko direktorioa ({$a->parent}) ez da idazteko modukoa. Instalatzaileak ezin du datu-direktorioa ({$a->dataroot}) sortu.';
|
||||
$string['pathssubadmindir'] = 'Web ostalari gutxi batzuk /admin URL berezi gisa erabiltzen dute kontrol-panel edo antzekora sarbidea emateko. Zoritxarrez, honek Moodleren kudeatze-orrien lehenetsitako kokapenarekin gatazka sortzen du. Hau konpondu dezakezu zure instalazioko admin direktorioa berrizendatuz, eta izen berria hemen sartuta. Adibidez <em>moodleadmin</em>. Honek Moodleko admin estekak konponduko du.';
|
||||
$string['pathssubadmindir'] = 'Web ostalari gutxi batzuk /admin URL berezi gisa erabiltzen dute kontrol-panel edo antzekora sarbidea emateko. Zoritxarrez, honek Moodleko kudeatze-orrien lehenetsitako kokapenarekin gatazka sortzen du. Hau konpondu dezakezu zure instalazioko admin direktorioa berrizendatuz, eta izen berria hemen sartuta. Adibidez <em>moodleadmin</em>. Honek Moodleko admin estekak konponduko du.';
|
||||
$string['pathssubdataroot'] = '<p>Moodlek erabiltzaileek igotako fitxategien edukiak bilduko dituen direktorio bat.</p>
|
||||
<p>Direktorio honetan web-zerbitzariaren erabiltzaileak irakurtzeko eta idazteko baimena izan beharko ditu (normalean \'www-data\', \'nobody\', edo \'apache\').</p>
|
||||
<p>Ez litzateke web bidez eskuragarri egon beharko.</p>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['language'] = 'Hizkuntza';
|
||||
$string['moodlelogo'] = 'Moodleren logoa';
|
||||
$string['moodlelogo'] = 'Moodleko logoa';
|
||||
$string['next'] = 'Hurrengoa';
|
||||
$string['previous'] = 'Aurrekoa';
|
||||
$string['reload'] = 'Berriz kargatu';
|
||||
|
||||
@@ -34,8 +34,8 @@ $string['cliansweryes'] = 's';
|
||||
$string['cliincorrectvalueerror'] = 'Error, valor incorrecte "{$a->value}" for "{$a->option}"';
|
||||
$string['cliincorrectvalueretry'] = 'Valor incorrecte, per favor essaya novemente';
|
||||
$string['clitypevalue'] = 'valor de typo';
|
||||
$string['clitypevaluedefault'] = 'valor de typo,pressa Enter per usar le vaor predefinite ({$a})';
|
||||
$string['cliunknowoption'] = 'Uotiones non recognoscite:
|
||||
$string['clitypevaluedefault'] = 'valor de typo, pressa Enter per usar le valor predefinite ({$a})';
|
||||
$string['cliunknowoption'] = 'Optiones non recognoscite:
|
||||
{$a}
|
||||
Per favor usa le option --help.';
|
||||
$string['cliyesnoprompt'] = 'typa s (significa si) o n (significa no)';
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['cannotcreatedboninstall'] = '<p>Non pote crear le base de datos.</p>
|
||||
<p>Le base de datos specificate non existe e le date usator non ha le permission a crear le base de datos.</p>
|
||||
<p>Le administrator de sito deberea verificar le configuration de base de datos.</p>';
|
||||
$string['cannotcreatelangdir'] = 'Non pote crear directorio de linguage (lang)';
|
||||
$string['cannotcreatetempdir'] = 'Non pote crear directorio temp';
|
||||
$string['cannotdownloadcomponents'] = 'Non pote discargar componentes';
|
||||
$string['cannotdownloadzipfile'] = 'Non pote discargar file ZIP';
|
||||
$string['cannotfindcomponent'] = 'Non pote trovar componente';
|
||||
$string['cannotsavemd5file'] = 'Non pote salveguardar file md5';
|
||||
$string['cannotsavezipfile'] = 'Non pote salveguardar file ZIP';
|
||||
$string['cannotunzipfile'] = 'Non pote extraher (unzip) file Zip';
|
||||
$string['componentisuptodate'] = 'Componente es actualisate';
|
||||
$string['dmlexceptiononinstall'] = '<p>Un error de base de datos ha occurrite [{$a->errorcode}].<br />{$a->debuginfo}</p>';
|
||||
$string['downloadedfilecheckfailed'] = 'Verifica de file discargate falleva';
|
||||
$string['invalidmd5'] = 'Le variabile de verifica esseva errate - essaya novemente';
|
||||
$string['missingrequiredfield'] = 'Alcun campo requirite es mancante';
|
||||
$string['remotedownloaderror'] = '<p>Le discargamento delcomponente sur tu servitor falleva. Per favor verifica preferentias de proxy; le extension de PHP cURL ed fortemente recommendate.</p>
|
||||
<p>Tu debe discargar le file <a href="{$a->url}">{$a->url}</a> manualmente, copiar lo a "{$a->dest}" in tu servitor e extraher lo (unzip) illac.</p>';
|
||||
$string['wrongdestpath'] = 'Percurso de destination errate';
|
||||
$string['wrongsourcebase'] = 'Base de URL de fonte errate';
|
||||
$string['wrongzipfilename'] = 'Nomine de file ZIP errate';
|
||||
@@ -84,10 +84,10 @@ $string['phpversionhelp'] = '<p>Moodleには少なくとも5.6.5または7.1のP
|
||||
<p>現在、あなたはバージョン {$a} を動作させています。</p>
|
||||
<p>PHPをアップグレードするか新しいバージョンのPHPがインストールされているホストに移動する必要があります。</p>';
|
||||
$string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
|
||||
$string['welcomep20'] = 'インストールが正常に完了して、あなたのコンピュータで <strong>{$a->packname} {$a->packversion}</strong> パッケージが起動されたため、このページが表示されています。おめでとうございます!';
|
||||
$string['welcomep30'] = 'このリリース <strong>{$a->installername}</strong> には<strong>Moodle</strong>で環境を作成するアプリケーションが含まれています。すなわち:';
|
||||
$string['welcomep40'] = 'パッケージには <strong>Moodle {$a->moodlerelease} ({$a->moodleversion})</strong> も含まれています。';
|
||||
$string['welcomep50'] = 'このパッケージ内のすべてのアプリケーションの使用は個別のライセンスによって規定されています。全体の<strong>{$a->installername}</strong>パッケージは<a href="https://www.opensource.org/docs/definition_plain.html">オープンソース</a>であり、<a href="https://www.gnu.org/copyleft/gpl.html">GPL</a>ライセンスの下で配布されています。';
|
||||
$string['welcomep20'] = 'インストール正常完了後、あなたのコンピュータで<strong>{$a->packname} {$a->packversion}</strong>パッケージが起動されたため、このページが表示されています。おめでとうございます!';
|
||||
$string['welcomep30'] = 'このリリース<strong>{$a->installername}</strong>には<strong>Moodle</strong>で環境を作成するアプリケーションが含まれています。すなわち:';
|
||||
$string['welcomep40'] = 'パッケージには<strong>Moodle {$a->moodlerelease} ({$a->moodleversion})</strong>も含まれています。';
|
||||
$string['welcomep50'] = 'このパッケージ内すべてのアプリケーションの使用は個別のライセンスにより規定されています。全体の<strong>{$a->installername}</strong>パッケージは<a href="https://www.opensource.org/docs/definition_plain.html">オープンソース</a>であり、<a href="https://www.gnu.org/copyleft/gpl.html">GPL</a>ライセンスの下で配布されています。';
|
||||
$string['welcomep60'] = '次からのページはあなたのコンピュータに<strong>Moodle</strong>を簡単に設定およびセットアップする手順にしたがって進みます。あなたはデフォルトの設定を使用することも、必要に応じて任意で設定を変更することもできます。';
|
||||
$string['welcomep70'] = '<strong>Moodle</strong>のセットアップを続けるには「次へ」ボタンをクリックしてください。';
|
||||
$string['wwwroot'] = 'ウェブアドレス';
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['language'] = 'Fiteny';
|
||||
$string['moodlelogo'] = 'Sarim-pamantarana ny Moodle';
|
||||
$string['next'] = 'Manaraka';
|
||||
$string['previous'] = 'Teo aloha';
|
||||
$string['reload'] = 'Avereno';
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['parentlanguage'] = 'pl';
|
||||
$string['thislanguage'] = 'Polski (Workplace)';
|
||||
@@ -1239,6 +1239,7 @@ $string['settingmaxinputvars'] = 'PHP setting max_input_vars is recommended to b
|
||||
$string['settingmaxinputvarsrequired'] = 'PHP setting max_input_vars must be at least 5000.';
|
||||
$string['settingmemorylimit'] = 'Insufficient memory detected, please set higher memory limit in PHP settings.';
|
||||
$string['settingsafemode'] = 'Moodle is not fully compatible with safe mode, please ask server administrator to turn it off. Running Moodle under safe mode is not supported, please expect various problems if you do so.';
|
||||
$string['settingzendexceptionignoreargs'] = 'It is strongly recommended that the PHP setting zend.exception_ignore_args be enabled as a security precaution.';
|
||||
$string['setupsearchengine'] = 'Setup search engine';
|
||||
$string['showcommentscount'] = 'Show comments count';
|
||||
$string['showdetails'] = 'Show details';
|
||||
|
||||
@@ -114,6 +114,7 @@ $string['america/chicago'] = 'America/Chicago';
|
||||
$string['america/chihuahua'] = 'America/Chihuahua';
|
||||
$string['america/ciudad_juarez'] = 'America/Ciudad_Juarez';
|
||||
$string['america/costa_rica'] = 'America/Costa_Rica';
|
||||
$string['america/coyhaique'] = 'America/Coyhaique';
|
||||
$string['america/creston'] = 'America/Creston';
|
||||
$string['america/cuiaba'] = 'America/Cuiaba';
|
||||
$string['america/curacao'] = 'America/Curacao';
|
||||
|
||||
@@ -136,7 +136,8 @@ class ADODB_postgres64 extends ADOConnection{
|
||||
// get the last id - never tested
|
||||
function pg_insert_id($tablename,$fieldname)
|
||||
{
|
||||
$result=pg_query($this->_connectionID, 'SELECT last_value FROM '. $tablename .'_'. $fieldname .'_seq');
|
||||
$sequence = pg_escape_identifier($this->_connectionID, $tablename .'_'. $fieldname .'_seq');
|
||||
$result = pg_query($this->_connectionID, 'SELECT last_value FROM '. $sequence);
|
||||
if ($result) {
|
||||
$arr = @pg_fetch_row($result,0);
|
||||
pg_free_result($result);
|
||||
|
||||
@@ -23,3 +23,7 @@ Removed:
|
||||
Added:
|
||||
* index.html - prevent directory browsing on misconfigured servers
|
||||
* readme_moodle.txt - this file ;-)
|
||||
|
||||
Changelog:
|
||||
* MDL-85375:
|
||||
- Fix SQL injection in pg_insert_id() - https://github.com/ADOdb/ADOdb/commit/11107d6d6e5160b62e05dff8a3a2678cf0e3a426
|
||||
|
||||
@@ -72,7 +72,6 @@ class behat_field_manager {
|
||||
* @return behat_form_field
|
||||
*/
|
||||
public static function get_form_field(NodeElement $fieldnode, Session $session) {
|
||||
|
||||
// Get the field type if is part of a moodleform.
|
||||
if (self::is_moodleform_field($fieldnode)) {
|
||||
$type = self::get_field_node_type($fieldnode, $session);
|
||||
|
||||
@@ -326,4 +326,13 @@ class behat_form_field implements behat_session_interface {
|
||||
|
||||
return $this->fieldlocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field node.
|
||||
*
|
||||
* @return NodeElement
|
||||
*/
|
||||
public function get_node(): NodeElement {
|
||||
return $this->field;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,6 +341,11 @@ function behat_is_test_site() {
|
||||
if (empty($CFG->behat_wwwroot)) {
|
||||
return false;
|
||||
}
|
||||
if (defined('CLI_SCRIPT') && CLI_SCRIPT && getenv('BEHAT_CLI')) {
|
||||
// Environment variable makes CLI script run on Behat instance.
|
||||
echo "BEHAT_CLI: This command line script is running on the acceptance testing site.\n\n";
|
||||
return true;
|
||||
}
|
||||
if (isset($_SERVER['REMOTE_ADDR']) and behat_is_requested_url($CFG->behat_wwwroot)) {
|
||||
// Something is accessing the web server like a real browser.
|
||||
return true;
|
||||
|
||||
@@ -756,6 +756,7 @@ class core_date {
|
||||
if ($intltz === null) {
|
||||
// Where intl doesn't know about a recent timezone, map it to an equivalent existing zone.
|
||||
$intltzname = strtr($tz->getName(), [
|
||||
'America/Coyhaique' => 'America/Santiago',
|
||||
'America/Ciudad_Juarez' => 'America/Denver',
|
||||
'America/Nuuk' => 'America/Godthab',
|
||||
'Europe/Kyiv' => 'Europe/Kiev',
|
||||
|
||||
@@ -84,8 +84,8 @@ class encryption {
|
||||
throw new \moodle_exception('encryption_keyalreadyexists', 'error');
|
||||
}
|
||||
|
||||
// Don't make it read-only in Behat or it will fail to clear for future runs.
|
||||
if (defined('BEHAT_SITE_RUNNING')) {
|
||||
// Don't make it read-only in tests or it will fail to clear for future runs.
|
||||
if (defined('BEHAT_SITE_RUNNING') || PHPUNIT_TEST) {
|
||||
$chmod = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,21 @@ class curl_security_helper extends curl_security_helper_base {
|
||||
'https' => 443
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string the host of the URL being checked by the helper.
|
||||
*/
|
||||
protected $host;
|
||||
|
||||
/**
|
||||
* @var array IP address or addresses the URL is allowed to be requested from (passed the blocked hosts check).
|
||||
*/
|
||||
protected $allowedips = [];
|
||||
|
||||
/**
|
||||
* @var ?int The port the URL is allowed to be requested from (passed the allowed port check).
|
||||
*/
|
||||
protected $allowedport;
|
||||
|
||||
/**
|
||||
* Checks whether the given URL is blocked by checking its address and port number against the allow/block lists.
|
||||
* The behaviour of this function can be classified as strict, as it returns true for URLs which are invalid or
|
||||
@@ -85,6 +100,8 @@ class curl_security_helper extends curl_security_helper_base {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->host = $parsed['host'];
|
||||
|
||||
// The port will be empty unless explicitly set in the $url (uncommon), so try to infer it from the supported schemes.
|
||||
if (!$parsed['port'] && $parsed['scheme'] && isset($this->transportschemes[$parsed['scheme']])) {
|
||||
$parsed['port'] = $this->transportschemes[$parsed['scheme']];
|
||||
@@ -162,12 +179,17 @@ class curl_security_helper extends curl_security_helper_base {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If any of the returned IPs are in the blocklist, block the request.
|
||||
// If any of the returned IPs are in the blocklist, block the request. Otherwise, temporarily record the IPs.
|
||||
$allowedips = [];
|
||||
foreach ($hostips as $hostip) {
|
||||
if ($this->address_explicitly_blocked($hostip)) {
|
||||
return true;
|
||||
}
|
||||
$allowedips[] = $hostip;
|
||||
}
|
||||
|
||||
// If none of the IPs are blocked, set them on the allow list so we can enforce them on subsequent requests.
|
||||
$this->allowedips = $allowedips;
|
||||
}
|
||||
} else {
|
||||
// Was not something we consider to be a valid IP or domain name, block it.
|
||||
@@ -201,7 +223,15 @@ class curl_security_helper extends curl_security_helper_base {
|
||||
return true;
|
||||
}
|
||||
$allowedports = $this->get_allowed_ports();
|
||||
return !empty($allowedports) && !in_array($portnum, $allowedports);
|
||||
|
||||
$isblocked = !empty($allowedports) && !in_array($portnum, $allowedports);
|
||||
|
||||
// If port is allowed, add it to our allow list so we can enforce it on subsequent requests.
|
||||
if (!$isblocked) {
|
||||
$this->allowedport = $portnum;
|
||||
}
|
||||
|
||||
return $isblocked;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,4 +321,23 @@ class curl_security_helper extends curl_security_helper_base {
|
||||
return !empty($entry);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper that returns host, IP and port information for the URL that has passed the blocked hosts/allowed ports checks.
|
||||
*
|
||||
* This data is in a format compatible with CURLOPT_RESOLVE, so it can be passed directly into that option.
|
||||
* Doing so will prevent cURL re-fetching the info from DNS, preventing subsequent requests to the remote host from
|
||||
* modifying the IP/port to ones that haven't been validated.
|
||||
*
|
||||
* @return array of strings in the format hostname:port:ip_address.
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function get_resolve_info(): array {
|
||||
if (empty($this->host || empty($this->allowedips) || empty($this->allowedport))) {
|
||||
$exception = 'In the curl_security_helper class, url_is_blocked() must be called before get_resolve_info() is called.';
|
||||
throw new \core\exception\coding_exception($exception);
|
||||
}
|
||||
|
||||
return array_map(fn($ip) => "$this->host:$this->allowedport:$ip", $this->allowedips);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,4 +676,16 @@ class core_text {
|
||||
|
||||
return mb_convert_case($text, MB_CASE_TITLE, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims control characters out of a string.
|
||||
* Example: (\x00-\x1f) and (\x7f)
|
||||
*
|
||||
* @param string $text Input string
|
||||
* @return string Cleaned string value
|
||||
*/
|
||||
public static function trim_ctrl_chars(string $text): string {
|
||||
// Remove control characters text.
|
||||
return preg_replace('/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]/i', '', $text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,11 @@ Feature: Add media to Atto
|
||||
And the field "Enter name" matches value "moodle-logo.webm"
|
||||
And I wait until the page is ready
|
||||
And I click on "Insert media" "button"
|
||||
When I click on "Save changes" "button"
|
||||
Then "//a[. = 'moodle-logo.webm']" "xpath_element" should exist
|
||||
And I click on "Show more buttons" "button"
|
||||
When I click on "HTML" "button"
|
||||
Then I should see "moodle-logo.webm\">moodle-logo.webm</a>"
|
||||
And I click on "Save changes" "button"
|
||||
And ".video-js" "css_element" should exist
|
||||
|
||||
@javascript @atto_media_video
|
||||
Scenario: Insert some media as a plain video
|
||||
|
||||
@@ -3163,6 +3163,8 @@ class curl {
|
||||
private $ignoresecurity;
|
||||
/** @var array $mockresponses For unit testing only - return the head of this list instead of making the next request. */
|
||||
private static $mockresponses = [];
|
||||
/** @var array $curlresolveinfo Resolve addresses for the URL that have passed cuRL security checks, in a CURLOPT_RESOLVE compatible format. */
|
||||
private $curlresolveinfo = [];
|
||||
|
||||
/**
|
||||
* Curl constructor.
|
||||
@@ -3759,6 +3761,9 @@ class curl {
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
// Set allowed resolve info if the URL is not blocked.
|
||||
$this->curlresolveinfo = $this->securityhelper->get_resolve_info();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3794,6 +3799,10 @@ class curl {
|
||||
// Set the URL as a curl option.
|
||||
$this->setopt(array('CURLOPT_URL' => $url));
|
||||
|
||||
// Force cURL to only resolve the URL from IP/port combinations that were validated by the security helper.
|
||||
// This prevents re-fetching DNS data on subsequent requests, which could return un-validated hosts/ports.
|
||||
$this->setopt(['CURLOPT_RESOLVE' => $this->curlresolveinfo]);
|
||||
|
||||
// Create curl instance.
|
||||
$curl = curl_init();
|
||||
|
||||
@@ -3904,6 +3913,10 @@ class curl {
|
||||
|
||||
curl_setopt($curl, CURLOPT_URL, $redirecturl);
|
||||
|
||||
// Force cURL to only resolve the URL from IP/port combinations that were validated by the security helper.
|
||||
// This prevents re-fetching DNS data on subsequent requests, which could return un-validated hosts/ports.
|
||||
$this->setopt(['CURLOPT_RESOLVE' => $this->curlresolveinfo]);
|
||||
|
||||
// If CURLOPT_UNRESTRICTED_AUTH is empty/false, don't send credentials to other hosts.
|
||||
// Ref: https://curl.se/libcurl/c/CURLOPT_UNRESTRICTED_AUTH.html.
|
||||
$isdifferenthost = parse_url($currenturl)['host'] !== parse_url($redirecturl)['host'];
|
||||
|
||||
+3
-3
@@ -1105,10 +1105,10 @@ function clean_param($param, $type) {
|
||||
} else if (preg_match('/^' . preg_quote($CFG->wwwroot . '/', '/') . '/i', $param)) {
|
||||
// Absolute, and matches our wwwroot.
|
||||
} else {
|
||||
|
||||
// Relative - let's make sure there are no tricks.
|
||||
if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?') && !preg_match('/javascript:/i', $param)) {
|
||||
// Looks ok.
|
||||
if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?') &&
|
||||
!preg_match('/javascript(?:.*\/{2,})?:/i', rawurldecode($param))) {
|
||||
// Valid relative local URL.
|
||||
} else {
|
||||
$param = '';
|
||||
}
|
||||
|
||||
@@ -32,26 +32,8 @@ namespace core;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class encryption_test extends \advanced_testcase {
|
||||
/**
|
||||
* Clear junk created by tests.
|
||||
*/
|
||||
protected function tearDown(): void {
|
||||
global $CFG;
|
||||
$keyfile = encryption::get_key_file(encryption::METHOD_OPENSSL);
|
||||
if (file_exists($keyfile)) {
|
||||
chmod($keyfile, 0700);
|
||||
}
|
||||
$keyfile = encryption::get_key_file(encryption::METHOD_SODIUM);
|
||||
if (file_exists($keyfile)) {
|
||||
chmod($keyfile, 0700);
|
||||
}
|
||||
remove_dir($CFG->dataroot . '/secret');
|
||||
unset($CFG->nokeygeneration);
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
$this->tearDown();
|
||||
|
||||
require_once(__DIR__ . '/fixtures/testable_encryption.php');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
This files describes API changes in core libraries and APIs,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 4.1.18 ===
|
||||
* A new method, `core_text::trim_ctrl_chars()`, has been introduced to clean control characters from text.
|
||||
This ensures cleaner input handling and prevents issues caused by invisible or non-printable characters
|
||||
|
||||
=== 4.1.12 ===
|
||||
|
||||
* Added the ability for unit tests to autoload classes in the `\[component]\tests\` namespace from the `[path/to/component]/tests/classes` directory.
|
||||
|
||||
+1
-2
@@ -2410,8 +2410,7 @@ function send_headers($contenttype, $cacheable = true) {
|
||||
@header('Expires: ');
|
||||
} else {
|
||||
// Do everything we can to always prevent clients and proxies caching.
|
||||
@header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
@header('Cache-Control: post-check=0, pre-check=0, no-transform', false);
|
||||
@header('Cache-Control: no-store, no-cache, must-revalidate, no-transform');
|
||||
@header('Pragma: no-cache');
|
||||
@header('Expires: Mon, 20 Aug 1969 09:23:00 GMT');
|
||||
@header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
@@ -49,6 +49,7 @@ $context = context_system::instance();
|
||||
$PAGE->set_url("$CFG->wwwroot/login/index.php");
|
||||
$PAGE->set_context($context);
|
||||
$PAGE->set_pagelayout('login');
|
||||
$PAGE->set_cacheable(false);
|
||||
|
||||
/// Initialize variables
|
||||
$errormsg = '';
|
||||
|
||||
@@ -308,8 +308,17 @@ class media_videojs_plugin extends core_media_player_native {
|
||||
|
||||
// Ogv.JS Tech.
|
||||
$this->ogvtech = false;
|
||||
if (in_array($ext, $this->ogvsupportedextensions) &&
|
||||
(core_useragent::is_safari() || core_useragent::is_ios())) {
|
||||
/* The following browsers do not support OGV natively:
|
||||
- Chrome (since version 120)
|
||||
- Edge (since version 122)
|
||||
- Safari
|
||||
- Safari on iOS
|
||||
- Firefox (since version 130)
|
||||
- Opera (since version 106)
|
||||
Refer: https://caniuse.com/ogv.
|
||||
We need to enable Ogv.JS Tech plugin for all browsers that do not support OGV natively.
|
||||
*/
|
||||
if (in_array($ext, $this->ogvsupportedextensions)) {
|
||||
$this->ogvtech = true;
|
||||
$result[] = $url;
|
||||
continue;
|
||||
|
||||
+30
-2
@@ -3388,7 +3388,7 @@ class core_message_external extends external_api {
|
||||
bool $includecontactrequests = false,
|
||||
bool $includeprivacyinfo = false
|
||||
) {
|
||||
global $CFG, $USER;
|
||||
global $CFG, $USER, $DB;
|
||||
|
||||
// All the business logic checks that really shouldn't be in here.
|
||||
if (empty($CFG->messaging)) {
|
||||
@@ -3408,9 +3408,37 @@ class core_message_external extends external_api {
|
||||
throw new moodle_exception('You do not have permission to perform this action.');
|
||||
}
|
||||
|
||||
// Return early if no userids are provided.
|
||||
if (empty($params['userids'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Filter the user IDs, removing the IDs of the users that the current user cannot view.
|
||||
require_once($CFG->dirroot . '/user/lib.php');
|
||||
$userfieldsapi = \core_user\fields::for_userpic()->including('username', 'deleted');
|
||||
$userfields = $userfieldsapi->get_sql('', false, '', '', false)->selects;
|
||||
$users = $DB->get_records_list('user', 'id', $userids, '', $userfields, 0, 100);
|
||||
$filteredids = array_filter($params['userids'], function($userid) use ($users, $params) {
|
||||
$targetuser = $users[$userid];
|
||||
// Check if the user has the contact already.
|
||||
$iscontact = \core_message\api::is_contact($params['referenceuserid'], $userid);
|
||||
if ($iscontact) {
|
||||
// User is a contact, so we can return the info for this user.
|
||||
return true;
|
||||
} else {
|
||||
// User is not a contact, so we need to check if the user is allowed to see the profile or not.
|
||||
return user_can_view_profile($targetuser);
|
||||
}
|
||||
});
|
||||
|
||||
// Return early if no user IDs are left after filtering.
|
||||
if (empty($filteredids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return \core_message\helper::get_member_info(
|
||||
$params['referenceuserid'],
|
||||
$params['userids'],
|
||||
$filteredids,
|
||||
$params['includecontactrequests'],
|
||||
$params['includeprivacyinfo']
|
||||
);
|
||||
|
||||
@@ -55,7 +55,7 @@ Feature: View activity completion in the assignment activity
|
||||
And the manual completion button of "Music history" is displayed as "Done"
|
||||
|
||||
@javascript
|
||||
Scenario: The manual completion button will not be shown on the course page if the Show activity completion conditions is set to No
|
||||
Scenario: Assign module manual completion button hidden if Show activity completion is set to No
|
||||
Given I am on the "Course 1" course page logged in as teacher1
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I expand all fieldsets
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
@mod @mod_assign
|
||||
Feature: Additional files for use in assignments can be hidden
|
||||
In order to display additional files only during submission
|
||||
As a teacher
|
||||
I need to check the 'Only show files during submission' checkbox
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | One | teacher1@example.com |
|
||||
| student1 | Student | One | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
|
||||
@javascript @_file_upload
|
||||
Scenario: Additional files are only shown during submission
|
||||
Given the following "activities" exist:
|
||||
| activity | course | name | submissionattachments | assignsubmission_onlinetext_enabled | assignsubmission_file_enabled |
|
||||
| assign | C1 | Assign 1 | 1 | 1 | 0 |
|
||||
And I am on the "Assign 1" "assign activity editing" page logged in as teacher1
|
||||
And I upload "/mod/assign/tests/fixtures/submissionsample01.txt" file to "Additional files" filemanager
|
||||
And I press "Save and return to course"
|
||||
When I am on the "Assign 1" "assign activity" page logged in as student1
|
||||
# Confirm that Additional files are not displayed on assignment activity when student logs in.
|
||||
Then "submissionsample01.txt" "link" should not exist
|
||||
And I press "Add submission"
|
||||
# Additional files are displayed after student presses "Add submission" button and goes to submission phase.
|
||||
And "submissionsample01.txt" "link" should exist
|
||||
And following "submissionsample01.txt" should download a file that:
|
||||
| Has mimetype | text/plain |
|
||||
| Contains text | This is just a submission testing sample. |
|
||||
@@ -68,6 +68,8 @@ $context = $instance->get_context();
|
||||
|
||||
require_login($course, true, $cm);
|
||||
|
||||
require_sesskey();
|
||||
|
||||
// Note : this uses the group optional_param as a value to decide which groupid.
|
||||
$groupid = groups_get_activity_group($cm, true) ?: null;
|
||||
if ($groupid) {
|
||||
@@ -144,14 +146,18 @@ switch (strtolower($action)) {
|
||||
break;
|
||||
|
||||
case 'play':
|
||||
$recording = recording::get_record(['id' => $rid]);
|
||||
if ($href = $recording->get_remote_playback_url($rtype)) {
|
||||
logger::log_recording_played_event($instance, $rid);
|
||||
redirect(urldecode($href));
|
||||
} else {
|
||||
$recordings = $instance->get_recordings();
|
||||
if (!isset($recordings[$rid])) {
|
||||
notification::add(get_string('recordingnotfound', 'mod_bigbluebuttonbn'), notification::ERROR);
|
||||
redirect($instance->get_view_url());
|
||||
}
|
||||
$href = $recordings[$rid]->get_remote_playback_url($rtype);
|
||||
if (!$href) {
|
||||
notification::add(get_string('recordingurlnotfound', 'mod_bigbluebuttonbn'), notification::ERROR);
|
||||
redirect($instance->get_view_url());
|
||||
}
|
||||
logger::log_recording_played_event($instance, $rid);
|
||||
redirect(urldecode($href));
|
||||
// We should never reach this point.
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1034,7 +1034,8 @@ EOF;
|
||||
return new moodle_url('/mod/bigbluebuttonbn/bbb_view.php', [
|
||||
'action' => 'logout',
|
||||
'id' => $this->cm->id,
|
||||
'courseid' => $this->cm->course // Used to find the course if ever the activity is deleted
|
||||
'courseid' => $this->cm->course, // Used to find the course if ever the activity is deleted
|
||||
'sesskey' => sesskey(),
|
||||
// while the meeting is running.
|
||||
]);
|
||||
}
|
||||
@@ -1073,6 +1074,7 @@ EOF;
|
||||
'action' => 'join',
|
||||
'id' => $this->cm->id,
|
||||
'bn' => $this->instancedata->id,
|
||||
'sesskey' => sesskey(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -577,6 +577,7 @@ class recording extends persistent {
|
||||
'bn' => $this->raw_get('bigbluebuttonbnid'),
|
||||
'rid' => $this->get('id'),
|
||||
'rtype' => $clone['type'],
|
||||
'sesskey' => sesskey(),
|
||||
]);
|
||||
|
||||
return $clone;
|
||||
|
||||
@@ -648,4 +648,5 @@ $string['taskname:check_dismissed_recordings'] = 'Check for recordings that have
|
||||
$string['userlimitreached'] = 'The number of users allowed in a session has been reached.';
|
||||
$string['waitformoderator'] = 'Waiting for a moderator to join.';
|
||||
|
||||
$string['recordingnotfound'] = 'The recording was not found.';
|
||||
$string['recordingurlnotfound'] = 'The recording URL is invalid.';
|
||||
|
||||
@@ -529,7 +529,9 @@ function mod_bigbluebuttonbn_core_calendar_provide_event_action(
|
||||
'action' => 'join',
|
||||
'id' => $cm->id,
|
||||
'bn' => $bigbluebuttonbn->id,
|
||||
'timeline' => 1]
|
||||
'timeline' => 1,
|
||||
'sesskey' => sesskey(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -745,7 +745,6 @@ class template {
|
||||
$editurl = new moodle_url('/mod/data/edit.php', $this->baseurl->params());
|
||||
$editurl->params([
|
||||
'rid' => $entry->id,
|
||||
'sesskey' => sesskey(),
|
||||
'backto' => urlencode($backurl->out(false))
|
||||
]);
|
||||
|
||||
@@ -758,7 +757,6 @@ class template {
|
||||
// Delete entry.
|
||||
$deleteurl = new moodle_url($this->baseurl, [
|
||||
'delete' => $entry->id,
|
||||
'sesskey' => sesskey(),
|
||||
'mode' => 'single',
|
||||
]);
|
||||
|
||||
|
||||
@@ -80,11 +80,6 @@ if ($manager->can_manage_templates()) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($rid) {
|
||||
// When editing an existing record, we require the session key.
|
||||
require_sesskey();
|
||||
}
|
||||
|
||||
// Get Group information for permission testing and record creation.
|
||||
$currentgroup = groups_get_activity_group($cm);
|
||||
$groupmode = groups_get_activity_groupmode($cm);
|
||||
|
||||
@@ -393,7 +393,7 @@ final class template_test extends \advanced_testcase {
|
||||
],
|
||||
'Teacher actionsmenu tag with default options' => [
|
||||
'templatecontent' => 'Some ##actionsmenu## tag',
|
||||
'expected' => '|Some .*edit.*{entryid}.*sesskey.*Edit.* .*delete.*{entryid}.*sesskey.*Delete.* tag|',
|
||||
'expected' => '|Some .*edit.*{entryid}.*Edit.* .*delete.*{entryid}.*Delete.* tag|',
|
||||
'rolename' => 'editingteacher',
|
||||
],
|
||||
'Teacher actionsmenu tag with default options (check Show more is not there)' => [
|
||||
@@ -689,7 +689,7 @@ final class template_test extends \advanced_testcase {
|
||||
],
|
||||
'Student actionsmenu tag with default options' => [
|
||||
'templatecontent' => 'Some ##actionsmenu## tag',
|
||||
'expected' => '|Some .*edit.*{entryid}.*sesskey.*Edit.* .*delete.*{entryid}.*sesskey.*Delete.* tag|',
|
||||
'expected' => '|Some .*edit.*{entryid}.*Edit.* .*delete.*{entryid}.*Delete.* tag|',
|
||||
'rolename' => 'student',
|
||||
],
|
||||
'Student actionsmenu tag with default options (check Show more is not there)' => [
|
||||
|
||||
+10
-5
@@ -243,7 +243,7 @@ if (!empty(trim($search))) {
|
||||
$PAGE->set_title(implode(moodle_page::TITLE_SEPARATOR, $titleparts));
|
||||
$PAGE->set_heading($course->fullname);
|
||||
$PAGE->force_settings_menu(true);
|
||||
if ($delete && confirm_sesskey() && (data_user_can_manage_entry($delete, $data, $context))) {
|
||||
if ($delete && data_user_can_manage_entry($delete, $data, $context)) {
|
||||
$PAGE->activityheader->disable();
|
||||
}
|
||||
|
||||
@@ -284,8 +284,10 @@ if ($data->intro and empty($page) and empty($record) and $mode != 'single') {
|
||||
|
||||
/// Delete any requested records
|
||||
|
||||
if ($delete && confirm_sesskey() && (data_user_can_manage_entry($delete, $data, $context))) {
|
||||
if ($delete && data_user_can_manage_entry($delete, $data, $context)) {
|
||||
if ($confirm) {
|
||||
require_sesskey();
|
||||
|
||||
if (data_delete_record($delete, $data, $course->id, $cm->id)) {
|
||||
echo $OUTPUT->notification(get_string('recorddeleted','data'), 'notifysuccess');
|
||||
}
|
||||
@@ -320,8 +322,10 @@ if ($serialdelete) {
|
||||
$multidelete = json_decode($serialdelete);
|
||||
}
|
||||
|
||||
if ($multidelete && confirm_sesskey() && $canmanageentries) {
|
||||
if ($confirm = optional_param('confirm', 0, PARAM_INT)) {
|
||||
if ($multidelete && $canmanageentries) {
|
||||
if ($confirm) {
|
||||
require_sesskey();
|
||||
|
||||
foreach ($multidelete as $value) {
|
||||
data_delete_record($value, $data, $course->id, $cm->id);
|
||||
}
|
||||
@@ -369,7 +373,8 @@ if ($showactivity) {
|
||||
// Approve or disapprove any requested records
|
||||
$approvecap = has_capability('mod/data:approve', $context);
|
||||
|
||||
if (($approve || $disapprove) && confirm_sesskey() && $approvecap) {
|
||||
if (($approve || $disapprove) && $approvecap) {
|
||||
require_sesskey();
|
||||
$newapproved = $approve ? true : false;
|
||||
$recordid = $newapproved ? $approve : $disapprove;
|
||||
if ($approverecord = $DB->get_record('data_records', array('id' => $recordid))) { // Need to check this is valid
|
||||
|
||||
@@ -6,19 +6,58 @@ Feature: In a lesson activity, teachers can review student attempts
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
| student2 | Student | 2 | student2@example.com |
|
||||
# Force group mode so you don't need to set it manually on the activity.
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
| fullname | shortname | category | groupmodeforce |
|
||||
| Course 1 | C1 | 0 | |
|
||||
| Course 2 | C2 | | 1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
| teacher1 | C2 | editingteacher |
|
||||
| student1 | C2 | student |
|
||||
| student2 | C2 | student |
|
||||
And the following "activities" exist:
|
||||
| activity | name | course | idnumber | retake |
|
||||
| lesson | Test lesson name | C1 | lesson1 | 1 |
|
||||
| activity | name | course | idnumber | retake |
|
||||
| lesson | Test lesson name | C1 | lesson1 | 1 |
|
||||
| lesson | Lesson 1 | C2 | | |
|
||||
And the following "groups" exist:
|
||||
| name | course | idnumber |
|
||||
| Group 1 | C2 | G1 |
|
||||
| Group 2 | C2 | G2 |
|
||||
And the following "group members" exist:
|
||||
| user | group |
|
||||
| student1 | G1 |
|
||||
| student2 | G2 |
|
||||
And the following "mod_lesson > page" exist:
|
||||
| lesson | qtype | title | content |
|
||||
| Lesson 1 | multichoice | Multichoice question | This is a multichoice question. |
|
||||
| Lesson 1 | essay | Essay question | Write an essay about anything. |
|
||||
| Lesson 1 | content | Content page | First page contents. |
|
||||
And the following "mod_lesson > answers" exist:
|
||||
| page | answer | jumpto | score |
|
||||
| Multichoice question | Correct answer | Next page | 1 |
|
||||
| Multichoice question | Incorrect answer | This page | 0 |
|
||||
| Essay question | | Next page | 1 |
|
||||
| Content page | Previous page | Previous page | 0 |
|
||||
| Content page | Next page | Next page | 0 |
|
||||
And I am on the "Lesson 1" "lesson activity" page logged in as student1
|
||||
And I set the following fields to these values:
|
||||
| Correct answer | 1 |
|
||||
And I press "Submit"
|
||||
And I set the field "Your answer" to "School is awesome!"
|
||||
And I press "Submit"
|
||||
And I am on the "Lesson 1" "lesson activity" page logged in as student2
|
||||
And I set the following fields to these values:
|
||||
| Incorrect answer | 1 |
|
||||
And I press "Submit"
|
||||
And I set the field "Your answer" to "Once upon an essay."
|
||||
And I press "Submit"
|
||||
|
||||
Scenario: View student attempts in a lesson containing both content and question pages
|
||||
Given the following "mod_lesson > pages" exist:
|
||||
@@ -96,3 +135,53 @@ Feature: In a lesson activity, teachers can review student attempts
|
||||
And I should not see "High score"
|
||||
And I should not see "Average score"
|
||||
And I should not see "Low score"
|
||||
|
||||
Scenario Outline: Teacher can filter lesson essay grading by group
|
||||
Given I am on the "C2" "course editing" page logged in as teacher1
|
||||
And I set the field "Group mode" to "<groupmode>"
|
||||
And I press "Save and display"
|
||||
And I am on the "Lesson 1" "lesson activity" page
|
||||
And I press "Grade essays"
|
||||
When I select "Group 1" from the "<groupmode>" singleselect
|
||||
Then I should see "Student 1"
|
||||
And I should not see "Student 2"
|
||||
And I select "Group 2" from the "<groupmode>" singleselect
|
||||
And I should see "Student 2"
|
||||
And I should not see "Student 1"
|
||||
And I select "All participants" from the "<groupmode>" singleselect
|
||||
And I should see "Student 1"
|
||||
And I should see "Student 2"
|
||||
|
||||
Examples:
|
||||
| groupmode |
|
||||
| Separate groups |
|
||||
| Visible groups |
|
||||
|
||||
Scenario Outline: Teacher can filter lesson reports by group
|
||||
Given I am on the "C2" "course editing" page logged in as teacher1
|
||||
And I set the field "Group mode" to "<groupmode>"
|
||||
And I press "Save and display"
|
||||
And I am on the "Lesson 1" "lesson activity" page
|
||||
And I navigate to "Reports" in current page administration
|
||||
When I select "Group 1" from the "<groupmode>" singleselect
|
||||
Then I should see "Student 1"
|
||||
And I should not see "Student 2"
|
||||
And I select "Group 2" from the "<groupmode>" singleselect
|
||||
And I should see "Student 2"
|
||||
And I should not see "Student 1"
|
||||
And I select "All participants" from the "<groupmode>" singleselect
|
||||
And I should see "Student 1"
|
||||
And I should see "Student 2"
|
||||
|
||||
Examples:
|
||||
| groupmode |
|
||||
| Separate groups |
|
||||
| Visible groups |
|
||||
|
||||
@javascript
|
||||
Scenario: Teacher can delete selected attempts
|
||||
Given I am on the "Lesson 1" "lesson activity" page logged in as teacher1
|
||||
And I navigate to "Reports" in current page administration
|
||||
When I click on "selectall-attempts" "checkbox"
|
||||
And I select "Delete selected" from the "With selected attempts..." singleselect
|
||||
Then I should see "No attempts have been made on this lesson."
|
||||
|
||||
@@ -14,9 +14,11 @@ Feature: View activity completion information in the Page resource
|
||||
| Course 1 | C1 | 0 | 1 | 1 |
|
||||
| Course 2 | C2 | 0 | 1 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student1 | C2 | student |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| teacher1 | C2 | editingteacher |
|
||||
|
||||
Scenario: A teacher can view a page resource automatic completion items
|
||||
Given the following "activity" exists:
|
||||
@@ -69,7 +71,7 @@ Feature: View activity completion information in the Page resource
|
||||
And I toggle the manual completion state of "Music history"
|
||||
And the manual completion button of "Music history" is displayed as "Done"
|
||||
|
||||
Scenario: The manual completion button will not be shown on the course page if the Show activity completion conditions is set to No as teacher
|
||||
Scenario Outline: Page module manual completion button hidden if Show activity completion is set to No
|
||||
Given the following "activity" exists:
|
||||
| activity | page |
|
||||
| course | C2 |
|
||||
@@ -77,16 +79,11 @@ Feature: View activity completion information in the Page resource
|
||||
| name | Music history |
|
||||
| intro | A lesson learned in life |
|
||||
| completion | 1 |
|
||||
When I am on the "Music history" "page activity" page logged in as teacher1
|
||||
When I am on the "Course 2" course page logged in as <user>
|
||||
# Course 2 has 'Show activity completion conditions' set to No, so the manual completion button should not be displayed.
|
||||
Then the manual completion button for "Music history" should not exist
|
||||
|
||||
Scenario: The manual completion button will not be shown on the course page if the Show activity completion conditions is set to No as student
|
||||
Given the following "activity" exists:
|
||||
| activity | page |
|
||||
| course | C2 |
|
||||
| idnumber | page1 |
|
||||
| name | Music history |
|
||||
| intro | A lesson learned in life |
|
||||
| completion | 1 |
|
||||
When I am on the "Music history" "page activity" page logged in as student1
|
||||
Then the manual completion button for "Music history" should not exist
|
||||
Examples:
|
||||
| user |
|
||||
| teacher1 |
|
||||
| student1 |
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
@mod @mod_quiz
|
||||
Feature: Blocks can be displayed on quiz attempt pages
|
||||
In order to see blocks on quiz attempt pages
|
||||
As a teacher
|
||||
I need to be able to set blocks display settings for quiz
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username |
|
||||
| student |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student | C1 | student |
|
||||
|
||||
Scenario Outline: Blocks display on quiz attempt pages can be toggled
|
||||
Given the following "activities" exist:
|
||||
| activity | course | name | idnumber | showblocks |
|
||||
| quiz | C1 | Quiz 1 | q1 | <blockdisplay> |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Activity module | q1 | Test questions |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name |
|
||||
| Test questions | truefalse | TF1 |
|
||||
And quiz "Quiz 1" contains the following questions:
|
||||
| question | page |
|
||||
| TF1 | 1 |
|
||||
And the following "blocks" exist:
|
||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
|
||||
| comments | Activity module | q1 | mod-quiz-attempt | side-pre |
|
||||
When I am on the "Quiz 1" "quiz activity" page logged in as student
|
||||
And I press "Attempt quiz"
|
||||
Then "Comments" "block" <blockvisibility> exist
|
||||
|
||||
Examples:
|
||||
| blockdisplay | blockvisibility |
|
||||
| 1 | should |
|
||||
| 0 | should not |
|
||||
@@ -157,13 +157,10 @@ class restore_qtype_match_plugin extends restore_qtype_plugin {
|
||||
$this->questionsubcacheid = $newquestionid;
|
||||
// Cache all cleaned answers and questiontext.
|
||||
foreach ($potentialsubs as $potentialsub) {
|
||||
// Clean in the same way than {@link xml_writer::xml_safe_utf8()}.
|
||||
$cleanquestion = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is',
|
||||
'', $potentialsub->questiontext); // Clean CTRL chars.
|
||||
$cleanquestion = core_text::trim_ctrl_chars($potentialsub->questiontext); // Clean CTRL chars.
|
||||
$cleanquestion = preg_replace("/\r\n|\r/", "\n", $cleanquestion); // Normalize line ending.
|
||||
|
||||
$cleananswer = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is',
|
||||
'', $potentialsub->answertext); // Clean CTRL chars.
|
||||
$cleananswer = core_text::trim_ctrl_chars($potentialsub->answertext); // Clean CTRL chars.
|
||||
$cleananswer = preg_replace("/\r\n|\r/", "\n", $cleananswer); // Normalize line ending.
|
||||
|
||||
$this->questionsubcache[$cleanquestion][$cleananswer] = $potentialsub->id;
|
||||
|
||||
@@ -37,7 +37,7 @@ Feature: Preview a Multiple choice question
|
||||
And I should see "Parts, but only parts, of your response are correct."
|
||||
|
||||
@javascript @_switch_window
|
||||
Scenario: Preview a Multiple choice question and submit a correct response.
|
||||
Scenario: Preview a Multiple choice question and submit multiple correct responses.
|
||||
When I am on the "Multi-choice-001" "core_question > preview" page logged in as teacher
|
||||
And I expand all fieldsets
|
||||
And I set the field "How questions behave" to "Immediate feedback"
|
||||
@@ -53,7 +53,7 @@ Feature: Preview a Multiple choice question
|
||||
And I should see "The correct answers are: One, Three"
|
||||
|
||||
@javascript @_switch_window
|
||||
Scenario: Preview a Multiple choice question and submit a correct response.
|
||||
Scenario: Preview a Multiple choice question and submit a single correct response.
|
||||
When I am on the "Multi-choice-002" "core_question > preview" page logged in as teacher
|
||||
And I expand all fieldsets
|
||||
And I set the field "How questions behave" to "Immediate feedback"
|
||||
|
||||
@@ -76,4 +76,52 @@ class behat_report_competency extends behat_base {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a competency filter.
|
||||
*
|
||||
* @When /^I set the competency filter "([^"]*)" to "([^"]*)"$/
|
||||
* @param string $fieldlocator The field locator.
|
||||
* @param string $value The value to set.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function set_competency_filter(
|
||||
string $fieldlocator,
|
||||
string $value
|
||||
): void {
|
||||
$field = behat_field_manager::get_form_field_from_label($fieldlocator, $this)->get_node();
|
||||
$session = $this->getSession();
|
||||
|
||||
$value = trim($value);
|
||||
|
||||
// Click into the field.
|
||||
$field->click();
|
||||
|
||||
// Remove any existing text.
|
||||
do {
|
||||
behat_base::type_keys($session, [behat_keys::BACKSPACE, behat_keys::DELETE]);
|
||||
} while (strlen($field->getValue()) > 0);
|
||||
$this->wait_for_pending_js();
|
||||
|
||||
// Type in the new value.
|
||||
behat_base::type_keys($session, str_split($value));
|
||||
$this->wait_for_pending_js();
|
||||
|
||||
// If the autocomplete found suggestions, then it will have:
|
||||
// 1) marked itself as expanded; and
|
||||
// 2) have an aria-selected suggestion in the list.
|
||||
$expanded = $field->getAttribute('aria-expanded');
|
||||
$suggestion = $field->getParent()->getParent()->find('css', '.form-autocomplete-suggestions > [aria-selected="true"]');
|
||||
|
||||
if ($expanded && null !== $suggestion) {
|
||||
// A suggestion was found.
|
||||
// Click on the first item in the list.
|
||||
$suggestion->click();
|
||||
} else {
|
||||
throw new \InvalidArgumentException(
|
||||
"Unable to find '{$value}' in the list of options."
|
||||
);
|
||||
}
|
||||
|
||||
$this->wait_for_pending_js();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ Feature: See the competencies for an activity
|
||||
Scenario: Go to the competency breakdown report
|
||||
When I navigate to "Reports" in current page administration
|
||||
And I click on "Competency breakdown" "link"
|
||||
And I set the field "Filter competencies by resource or activity" to "PageName1"
|
||||
And I set the competency filter "Filter competencies by resource or activity" to "PageName1"
|
||||
Then I should see "Test-Comp1"
|
||||
And I should not see "Test-Comp2"
|
||||
And I should see "Ann, Jill, Grainne, Beauchamp"
|
||||
|
||||
@@ -111,7 +111,7 @@ class repository_dropbox extends repository {
|
||||
*/
|
||||
public function get_reference_details($reference, $filestatus = 0) {
|
||||
global $USER;
|
||||
$ref = unserialize($reference);
|
||||
$ref = unserialize_object($reference);
|
||||
$detailsprefix = $this->get_name();
|
||||
if (isset($ref->userid) && $ref->userid != $USER->id && isset($ref->username)) {
|
||||
$detailsprefix .= ' ('.$ref->username.')';
|
||||
@@ -343,8 +343,8 @@ class repository_dropbox extends repository {
|
||||
* @return string New serialized reference
|
||||
*/
|
||||
protected function fix_old_style_reference($packed) {
|
||||
$ref = unserialize($packed);
|
||||
$ref = $this->dropbox->get_file_share_info($ref->path);
|
||||
$ref = unserialize_object($packed);
|
||||
$ref = $this->dropbox->get_file_share_info($ref->path ?? '');
|
||||
if (!$ref || empty($ref->url)) {
|
||||
// Some error occurred, do not fix reference for now.
|
||||
return $packed;
|
||||
@@ -396,10 +396,10 @@ class repository_dropbox extends repository {
|
||||
* @return object The unpacked reference
|
||||
*/
|
||||
protected function unpack_reference($packed) {
|
||||
$reference = unserialize($packed);
|
||||
$reference = unserialize_object($packed);
|
||||
if (empty($reference->url)) {
|
||||
// The reference is missing some information. Attempt to update it.
|
||||
return unserialize($this->fix_old_style_reference($packed));
|
||||
return unserialize_object($this->fix_old_style_reference($packed));
|
||||
}
|
||||
|
||||
return $reference;
|
||||
|
||||
@@ -163,6 +163,17 @@ class repository_equella extends repository {
|
||||
return ($countfailures[$sess] < 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned unserialized object from base64 encoded file reference data
|
||||
*
|
||||
* @param string $reference
|
||||
* @return stdClass
|
||||
*/
|
||||
private function unserialize_reference(string $reference): stdClass {
|
||||
$decoded = base64_decode($reference);
|
||||
return unserialize_object($decoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file, this function can be overridden by subclass. {@link curl}
|
||||
*
|
||||
@@ -175,7 +186,7 @@ class repository_equella extends repository {
|
||||
*/
|
||||
public function get_file($reference, $filename = '') {
|
||||
global $USER, $CFG;
|
||||
$ref = @unserialize(base64_decode($reference));
|
||||
$ref = $this->unserialize_reference($reference);
|
||||
if (!isset($ref->url) || !($url = $this->appendtoken($ref->url))) {
|
||||
// Occurs when the user isn't known..
|
||||
return null;
|
||||
@@ -201,7 +212,7 @@ class repository_equella extends repository {
|
||||
// if we had several unsuccessfull attempts to connect to server - do not try any more.
|
||||
return false;
|
||||
}
|
||||
$ref = @unserialize(base64_decode($file->get_reference()));
|
||||
$ref = $this->unserialize_reference($file->get_reference());
|
||||
if (!isset($ref->url) || !($url = $this->appendtoken($ref->url))) {
|
||||
// Occurs when the user isn't known..
|
||||
$file->set_missingsource();
|
||||
@@ -248,9 +259,8 @@ class repository_equella extends repository {
|
||||
* @param array $options additional options affecting the file serving
|
||||
*/
|
||||
public function send_file($stored_file, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
|
||||
$reference = unserialize(base64_decode($stored_file->get_reference()));
|
||||
$url = $this->appendtoken($reference->url);
|
||||
if ($url) {
|
||||
$ref = $this->unserialize_reference($stored_file->get_reference());
|
||||
if (isset($ref->url) && $url = $this->appendtoken($ref->url)) {
|
||||
header('Location: ' . $url);
|
||||
} else {
|
||||
send_file_not_found();
|
||||
@@ -421,8 +431,8 @@ class repository_equella extends repository {
|
||||
*/
|
||||
public function get_reference_details($reference, $filestatus = 0) {
|
||||
if (!$filestatus) {
|
||||
$ref = unserialize(base64_decode($reference));
|
||||
return $this->get_name(). ': '. $ref->filename;
|
||||
$ref = $this->unserialize_reference($reference);
|
||||
return $this->get_name(). ': '. ($ref->filename ?? '');
|
||||
} else {
|
||||
return get_string('lostsource', 'repository', '');
|
||||
}
|
||||
|
||||
+1
-1
@@ -15,4 +15,4 @@ Policy: https://moodledev.io/general/development/process/security
|
||||
|
||||
# Expiry date of this document
|
||||
# This information is considered current and up to date until 3 weeks after the next major Moodle LMS release
|
||||
Expires: 2025-05-05T01:00:00.000Z
|
||||
Expires: 2025-10-27T01:00:00.000Z
|
||||
|
||||
@@ -89,8 +89,9 @@ class behat_theme_classic_behat_admin extends behat_admin {
|
||||
}
|
||||
|
||||
$this->execute('behat_forms::i_set_the_field_with_xpath_to', [$fieldxpath, $value]);
|
||||
|
||||
$this->execute("behat_general::i_click_on", [get_string('savechanges'), 'button']);
|
||||
// Wait for the page to be redirected.
|
||||
$this->execute("behat_general::i_wait_to_be_redirected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -29,9 +29,9 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2022112817.00; // 20221128 = branching date YYYYMMDD - do not modify!
|
||||
$version = 2022112819.00; // 20221128 = branching date YYYYMMDD - do not modify!
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '4.1.17 (Build: 20250317)'; // Human-friendly version name
|
||||
$release = '4.1.19 (Build: 20250609)'; // Human-friendly version name
|
||||
$branch = '401'; // This version's branch.
|
||||
$maturity = MATURITY_STABLE; // This version's maturity level.
|
||||
|
||||
Reference in New Issue
Block a user