Compare commits

..

39 Commits

Author SHA1 Message Date
Jun Pataleta 2a900d6ed9 Moodle release 4.1.18 2025-04-13 09:52:10 +08:00
Huong Nguyen 385721f105 weekly release 4.1.17+ 2025-04-11 22:02:30 +07:00
Huong Nguyen 83c5af5001 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2025-04-11 22:02:29 +07:00
Sara Arjona 1b995b811d MDL-83994 course: Adapt PHPUnit to lower PHP versions 2025-04-11 19:24:57 +08:00
Huong Nguyen 52ad957d74 MDL-72704 message: Fix fetching for user has already contacted 2025-04-11 17:46:51 +08:00
Paul Holden edb68975d1 MDL-84865 cohort: improve system report validation of parameters. 2025-04-11 12:47:07 +08:00
Jun Pataleta 9d84286479 MDL-72704 message: Additional optimisation tweaks
* Return early if the `userids` params is empty or if the filtered
user IDs end up being empty.
2025-04-11 12:19:28 +08:00
Stevani Andolo 7b8be76dd9 MDL-72704 message: Fix fetching of conversation member info 2025-04-11 12:19:28 +08:00
raortegar c2bcdedbd6 MDL-85104 lib: Add additional check to local URLs params. 2025-04-11 10:22:58 +07:00
James C ca40241461 MDL-83994 course: Respect course_can_delete_section() in AJAX 2025-04-11 13:07:26 +10:00
Paul Holden 5e7ec29c65 MDL-84473 repository_equella: safer unserializing of file references. 2025-04-11 09:52:40 +07:00
Paul Holden 193e6b0fad MDL-84475 repository_dropbox: safer unserializing of file references. 2025-04-11 09:34:44 +07:00
Michael Hawkins b7a8375e65 MDL-85152 filter_tex: Update deny list and slash handling 2025-04-10 14:21:39 +08:00
Paul Holden 10e93d074b MDL-84478 tool_brickfield: verify sesskey prior to course analysis. 2025-04-10 13:07:36 +08:00
Paul Holden 0e6246db29 MDL-84499 block_rss_client: user access checks for viewing feed. 2025-04-10 12:32:57 +08:00
Paul Holden 58efa0fa10 MDL-65356 mod_data: update entry editing/deletion to not leak sesskey. 2025-04-10 10:08:23 +07:00
Paul Holden afc3bfc296 MDL-84750 course: require user be able to view course participants. 2025-04-10 09:31:11 +08:00
Paul Holden 9af8958b03 MDL-84479 tool_usertours: verify sesskey prior to tour duplication. 2025-04-08 16:16:42 +07:00
AMOS bot 896da8f473 Automatically generated installer lang files 2025-04-08 00:07:36 +00:00
Huong Nguyen 90fa1113f2 weekly release 4.1.17+ 2025-04-04 22:20:22 +07:00
Jun Pataleta aaf985001e Merge branch 'MDL-84907-401' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2025-04-04 11:31:11 +08:00
Shamim Rezaie 9b81be597c Merge branch 'MDL-85000-401' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2025-04-03 06:14:42 +11:00
Huong Nguyen 998f6ce060 MDL-85000 media_videojs: Enable Ogv.JS Tech for all browsers 2025-04-02 13:10:37 +07:00
Sara Arjona 881ad82c44 weekly release 4.1.17+ 2025-03-28 12:48:32 +01:00
Sara Arjona 5c163fff89 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2025-03-28 12:48:31 +01:00
Paul Holden 1ac64c87f9 MDL-85048 core: define mapping for new America/Coyhaique zone.
Follows same process as other timezone additions in d5ec3594.
2025-03-28 08:58:49 +00:00
Huong Nguyen ce35013ebf Merge branch 'MDL-85048-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2025-03-28 15:01:38 +07:00
Simey Lameze 8de6b2d6f4 MDL-85048 phpunit: add america/coyhaique timezone 2025-03-28 15:00:39 +08:00
AMOS bot 81fc25e7fa Automatically generated installer lang files 2025-03-28 00:08:28 +00:00
Huong Nguyen 5efe1cbf3c weekly release 4.1.17+ 2025-03-26 08:56:13 +07:00
Huong Nguyen 5deff67cea Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2025-03-26 08:56:12 +07:00
Huong Nguyen 14cb950ff3 Merge branch 'MDL-83160-401' of https://github.com/mickhawkins/moodle into MOODLE_401_STABLE 2025-03-24 14:55:08 +07:00
AMOS bot 374028b418 Automatically generated installer lang files 2025-03-22 00:07:37 +00:00
Huong Nguyen f0133eaae1 weekly release 4.1.17+ 2025-03-21 22:04:26 +07:00
Huong Nguyen 18998a6051 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2025-03-21 22:04:25 +07:00
AMOS bot 00b94dec34 Automatically generated installer lang files 2025-03-21 00:07:37 +00:00
Huong Nguyen 71269d9c8f MDL-84905 Behat: Fix I set the following administration settings failure 2025-03-19 09:17:38 +07:00
Huong Nguyen 61c44c40d8 MDL-84907 backup: Fix invalid CTRL characters regex 2025-03-18 14:13:52 +07:00
Michael Hawkins 4629c45815 MDL-83160 core: Update security.txt expiry 2025-03-13 20:36:10 +08:00
38 changed files with 411 additions and 95 deletions
+2
View File
@@ -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");
}
}
+1
View File
@@ -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());
+2 -3
View File
@@ -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;
}
/**
+2 -1
View File
@@ -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;
}
+1 -2
View File
@@ -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;
+3 -3
View File
@@ -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);
}
+16 -6
View File
@@ -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
View File
@@ -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)) {
+12 -6
View File
@@ -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) {
+3
View File
@@ -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) {
+120
View File
@@ -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;
}
}
+6
View File
@@ -3742,6 +3742,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
View File
@@ -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 (&#92;) versions of backslashed items to deny list.
$encodedslashdenylist = array_map(function($value) {
$encoded = str_replace('\\', '&#92;', $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';
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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';
+4 -4
View File
@@ -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'] = 'ウェブアドレス';
+36
View File
@@ -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';
+1
View File
@@ -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';
+1
View File
@@ -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',
+12
View File
@@ -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
+3 -3
View File
@@ -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 = '';
}
+4
View File
@@ -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.
+11 -2
View File
@@ -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
View File
@@ -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']
);
-2
View File
@@ -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',
]);
-5
View File
@@ -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);
+2 -2
View File
@@ -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
View File
@@ -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
@@ -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;
+5 -5
View File
@@ -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;
+17 -7
View File
@@ -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
View File
@@ -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
View File
@@ -29,9 +29,9 @@
defined('MOODLE_INTERNAL') || die();
$version = 2022112817.00; // 20221128 = branching date YYYYMMDD - do not modify!
$version = 2022112818.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.18 (Build: 20250414)'; // Human-friendly version name
$branch = '401'; // This version's branch.
$maturity = MATURITY_STABLE; // This version's maturity level.