From b564a40d741815364ebd93045eeae59755004204 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Sun, 7 Apr 2024 23:31:26 +0800 Subject: [PATCH 01/44] MDL-81457 core: Do not mark tests as Incomplete when they are wrong This check was marking a test as Incomplete if there was a bug in any part of the test, rather than failing the test in such a way that the test bug may be found. https://docs.phpunit.de/en/9.6/incomplete-and-skipped-tests.html --- enrol/tests/externallib_test.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enrol/tests/externallib_test.php b/enrol/tests/externallib_test.php index f42f6dba643f1..db511e5258da6 100644 --- a/enrol/tests/externallib_test.php +++ b/enrol/tests/externallib_test.php @@ -327,8 +327,9 @@ public function test_get_enrolled_users_visibility($settings, $results) { $this->expectExceptionMessage($exception['message']); } else { // Failed, only canview and exception are supported. - $this->markTestIncomplete('Incomplete, only canview and exception are supported'); + throw new \coding_exception('Incomplete, only canview and exception are supported'); } + // Switch to the user and assign the role. $this->setUser(${$user}); role_assign($roleid, $USER->id, $coursecontext); From 58acb96ee61ac41b11e19b2195b4a64bca97854f Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 13 Mar 2024 08:59:00 +0800 Subject: [PATCH 02/44] MDL-81209 core: Unit test should not care about plugin details --- lib/tests/component_test.php | 152 +++++++++++++++++++++++++++-------- 1 file changed, 119 insertions(+), 33 deletions(-) diff --git a/lib/tests/component_test.php b/lib/tests/component_test.php index ad9ae0b8139e4..9a1e11cb4bf5f 100644 --- a/lib/tests/component_test.php +++ b/lib/tests/component_test.php @@ -22,8 +22,7 @@ * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class component_test extends advanced_testcase { - +final class component_test extends advanced_testcase { /** * To be changed if number of subsystems increases/decreases, * this is defined here to annoy devs that try to add more without any thinking, @@ -491,8 +490,10 @@ public function test_get_plugin_list_with_file() { $this->assertEquals(array(), array_keys($list)); } - public function test_get_component_classes_in_namespace() { - + /** + * Tests for get_componenet_classes_in_namespace. + */ + public function test_get_component_classes_in_namespace(): void { // Unexisting. $this->assertCount(0, core_component::get_component_classes_in_namespace('core_unexistingcomponent', 'something')); $this->assertCount(0, core_component::get_component_classes_in_namespace('auth_cas', 'something')); @@ -502,35 +503,6 @@ public function test_get_component_classes_in_namespace() { $this->assertCount(0, core_component::get_component_classes_in_namespace('core_user', 'course')); $this->assertCount(0, core_component::get_component_classes_in_namespace('mod_forum', 'output\\emaildigest')); $this->assertCount(0, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\emaildigest')); - $this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', 'output\\email')); - $this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\email')); - $this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', 'output\\email\\')); - $this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\email\\')); - - // Prefix with backslash if it doesn\'t come prefixed. - $this->assertCount(1, core_component::get_component_classes_in_namespace('auth_cas', 'task')); - $this->assertCount(1, core_component::get_component_classes_in_namespace('auth_cas', '\\task')); - - // Core as a component works, the function can normalise the component name. - $this->assertCount(7, core_component::get_component_classes_in_namespace('core', 'update')); - $this->assertCount(7, core_component::get_component_classes_in_namespace('', 'update')); - $this->assertCount(7, core_component::get_component_classes_in_namespace('moodle', 'update')); - - // Multiple levels. - $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', '\\output\\myprofile\\')); - $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', 'output\\myprofile\\')); - $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', '\\output\\myprofile')); - $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', 'output\\myprofile')); - - // Without namespace it returns classes/ classes. - $this->assertCount(5, core_component::get_component_classes_in_namespace('tool_mobile', '')); - $this->assertCount(2, core_component::get_component_classes_in_namespace('tool_filetypes')); - - // When no component is specified, classes are returned for the namespace in all components. - // (We don't assert exact amounts here as the count of `output` classes will change depending on plugins installed). - $this->assertGreaterThan( - count(\core_component::get_component_classes_in_namespace('core', 'output')), - count(\core_component::get_component_classes_in_namespace(null, 'output'))); // Without either a component or namespace it returns an empty array. $this->assertEmpty(\core_component::get_component_classes_in_namespace()); @@ -538,6 +510,120 @@ public function test_get_component_classes_in_namespace() { $this->assertEmpty(\core_component::get_component_classes_in_namespace(null, '')); } + /** + * Test that the get_component_classes_in_namespace() function returns classes in the correct namespace. + * + * @dataProvider get_component_classes_in_namespace_provider + * @param array $methodargs + * @param string $expectedclassnameformat + */ + public function test_get_component_classes_in_namespace_provider( + array $methodargs, + string $expectedclassnameformat + ): void { + $classlist = core_component::get_component_classes_in_namespace(...$methodargs); + $this->assertGreaterThan(0, count($classlist)); + + foreach (array_keys($classlist) as $classname) { + $this->assertStringMatchesFormat($expectedclassnameformat, $classname); + } + } + + /** + * Data provider for get_component_classes_in_namespace tests. + * + * @return array + */ + public static function get_component_classes_in_namespace_provider(): array { + return [ + // Matches the last namespace level name not partials. + [ + ['mod_forum', 'output\\email'], + 'mod_forum\output\email\%s', + ], + [ + ['mod_forum', '\\output\\email'], + 'mod_forum\output\email\%s', + ], + [ + ['mod_forum', 'output\\email\\'], + 'mod_forum\output\email\%s', + ], + [ + ['mod_forum', '\\output\\email\\'], + 'mod_forum\output\email\%s', + ], + // Prefix with backslash if it doesn\'t come prefixed. + [ + ['auth_cas', 'task'], + 'auth_cas\task\%s', + ], + [ + ['auth_cas', '\\task'], + 'auth_cas\task\%s', + ], + + // Core as a component works, the function can normalise the component name. + [ + ['core', 'update'], + 'core\update\%s', + ], + [ + ['', 'update'], + 'core\update\%s', + ], + [ + ['moodle', 'update'], + 'core\update\%s', + ], + + // Multiple levels. + [ + ['core_user', '\\output\\myprofile\\'], + 'core_user\output\myprofile\%s', + ], + [ + ['core_user', 'output\\myprofile\\'], + 'core_user\output\myprofile\%s', + ], + [ + ['core_user', '\\output\\myprofile'], + 'core_user\output\myprofile\%s', + ], + [ + ['core_user', 'output\\myprofile'], + 'core_user\output\myprofile\%s', + ], + + // Without namespace it returns classes/ classes. + [ + ['tool_mobile', ''], + 'tool_mobile\%s', + ], + [ + ['tool_filetypes'], + 'tool_filetypes\%s', + ], + + // Multiple levels. + [ + ['core_user', '\\output\\myprofile\\'], + 'core_user\output\myprofile\%s', + ], + + // When no component is specified, classes are returned for the namespace in all components. + // (We don't assert exact amounts here as the count of `output` classes will change depending on plugins installed). + [ + ['core', 'output'], + 'core\%s', + ], + [ + [null, 'output'], + '%s', + ], + ]; + } + /** * Data provider for classloader test */ From 61fabd3e73ee04576f6573b2bd94aa8ef2a57589 Mon Sep 17 00:00:00 2001 From: AMOS bot Date: Mon, 22 Apr 2024 00:10:24 +0000 Subject: [PATCH 03/44] Automatically generated installer lang files --- install/lang/pt/install.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/install/lang/pt/install.php b/install/lang/pt/install.php index 314f4ce8da30e..b551bb4c06261 100644 --- a/install/lang/pt/install.php +++ b/install/lang/pt/install.php @@ -32,7 +32,7 @@ $string['admindirname'] = 'Pasta de administração'; $string['availablelangs'] = 'Pacotes linguísticos disponíveis'; $string['chooselanguagehead'] = 'Selecione um idioma'; -$string['chooselanguagesub'] = 'Selecione o idioma a utilizar durante a instalação. O idioma escolhido será definido como o predefinido mas poderá depois selecionar outro(s) idioma(s) para o site e para os utilizadores.'; +$string['chooselanguagesub'] = 'Selecione o idioma a utilizar durante a instalação. O idioma escolhido será definido como o predefinido, mas poderá depois selecionar outro(s) idioma(s) para o site e para os utilizadores.'; $string['clialreadyconfigured'] = 'O ficheiro config.php já existe. Use \'admin/cli/install_database.php\' para instalar o Moodle para este site.'; $string['clialreadyinstalled'] = 'O ficheiro config.php já existe, use admin/cli/install_database.php para atualizar o Moodle para este site.'; $string['cliinstallheader'] = 'Programa para instalação do Moodle {$a} através da linha de comandos'; @@ -56,9 +56,9 @@ $string['pathsrodataroot'] = 'A pasta dos dados é só de leitura (não permite escrita).'; $string['pathsroparentdataroot'] = 'A pasta ascendente {$a->parent} não tem permissões de escrita. O programa de instalação não conseguiu criar a pasta {$a->dataroot}.'; $string['pathssubadmindir'] = 'Alguns servidores Web utilizam a pasta admin em URLs especiais de acesso a funcionalidades especiais, como é o caso de painéis de controlo. Algumas situações podem criar conflitos com a localização normal das páginas de administração do Moodle. Estes problemas podem ser resolvidos renomeando a pasta admin na instalação do Moodle e indicando aqui o novo nome a utilizar. Exemplo:

moodleadmin

Esta ação resolverá os problemas de acesso das hiperligações para as funcionalidades de administração do Moodle.'; -$string['pathssubdataroot'] = '

Pasta onde o Moodle irá armazenar todo o conteúdo de ficheiros enviados pelos utilizadores.

-

O utilizador do Moodle no servidor web (normalmente nobody, apache ou www-data) deve ter permissão de leitura e escrita nessa pasta. -

Não deve ser acessível diretamente através da web.

+$string['pathssubdataroot'] = '

Pasta onde o Moodle irá armazenar todo o conteúdo dos ficheiros enviados pelos utilizadores.

+

O utilizador do Moodle no servidor web (normalmente nobody, apache ou www-data) deve ter permissão de leitura e de escrita nessa pasta. +

Não pode ser acessível diretamente através da web.

Se a pasta não existir, o processo de instalação tentará criá-la.

'; $string['pathssubdirroot'] = 'Caminho completo para a pasta que contém o código Moodle.'; $string['pathssubwwwroot'] = 'Endereço web completo de acesso ao Moodle. Não é possível aceder ao Moodle usando mais do que um endereço. Se o site tiver mais do que um endereço público, devem ser configurados redirecionamentos permanentes em todos eles, à exceção deste. Se o site pode ser acedido a partir da Internet e de Intranet, use o endereço público aqui. Se o endereço atual não está correto, altere o endereço indicado na barra de endereço do seu navegador e reinicie a instalação.'; From 1d609ca0eefbc3e6548d1dcf01b92a649070cc16 Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Thu, 16 Nov 2023 18:45:38 +0800 Subject: [PATCH 04/44] MDL-80107 gradingform: Behat test for defining marking guide --- .../tests/behat/define_marking_guide.feature | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 grade/grading/form/guide/tests/behat/define_marking_guide.feature diff --git a/grade/grading/form/guide/tests/behat/define_marking_guide.feature b/grade/grading/form/guide/tests/behat/define_marking_guide.feature new file mode 100644 index 0000000000000..04ccd8b9d76fe --- /dev/null +++ b/grade/grading/form/guide/tests/behat/define_marking_guide.feature @@ -0,0 +1,74 @@ +@gradingform @gradingform_guide +Feature: Teacher can define a marking guide + As a teacher, + I should be able to define a marking guide + + Background: + Given the following "users" exist: + | username | firtname | lastname | email | + | teacher1 | Teacher | One | teacher1@example.com | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + And the following "activities" exist: + | activity | course | name | advancedgradingmethod_submissions | + | assign | C1 | Assign 1 | guide | + And I am on the "Course 1" course page logged in as teacher1 + And I go to "Assign 1" advanced grading definition page + And I set the following fields to these values: + | Name | Marking guide 1 | + + Scenario: No criterion added to marking guide + When I press "Save as draft" + # Confirm that criterion parameters are required + Then I should see "Criterion name can not be empty" + And I should see "Criterion max score can not be empty" + # Confirm that marking guide is not saved due to the missing criterion + And I should not see "Marking guide 1 Draft" + And I should not see "Please note: the advanced grading form is not ready at the moment. Simple grading method will be used until the form has a valid status." + + @javascript + Scenario: Marking guide criterion is added to marking guide + Given I define the following marking guide: + | Criterion name | Description for students | Description for markers | Maximum score | + | Criteria 1 | Criteria 1 description for student | Criteria 1 description for marker | 70 | + | Criteria 2 | Criteria 2 description for student | Criteria 2 description for marker | 30 | + # Move Criteria 1 below Criteria 2 + And I click on "Move down" "button" in the "Criteria 1" "table_row" + When I press "Save as draft" + And I go to "Assign 1" advanced grading definition page + # Confirm that the order of criterion shown matches input -- Criteria 2 is listed before Criteria 1 + Then "Move down" "button" in the "Criteria 2" "table_row" should be visible + And "Move up" "button" in the "Criteria 2" "table_row" should not be visible + And "Move up" "button" in the "Criteria 1" "table_row" should be visible + And "Move down" "button" in the "Criteria 1" "table_row" should not be visible + # Confirm the other information entered were saved + And I should see "Criteria 2 description for student" in the "Criteria 2" "table_row" + And I should see "Criteria 2 description for marker" in the "Criteria 2" "table_row" + And I should see "30" in the "Criteria 2" "table_row" + And I should see "Criteria 1 description for student" in the "Criteria 1" "table_row" + And I should see "Criteria 1 description for marker" in the "Criteria 1" "table_row" + And I should see "70" in the "Criteria 1" "table_row" + + Scenario: Marking guide options and frequently used comment are added to marking guide + Given I define the following marking guide: + | Criterion name | Description for students | Description for markers | Maximum score | + | Criteria 1 | Criteria 1 description for student | Criteria 1 description for marker | 50 | + | Criteria 2 | Criteria 2 description for student | Criteria 2 description for marker | 50 | + # Add frequently used comments and other marking guide options + And I define the following frequently used comments: + | Comment 1 | + | Comment 2 | + And I set the following fields to these values: + | Show guide definition to students | 1 | + | Show marks per criterion to students | 0 | + When I press "Save as draft" + And I go to "Assign 1" advanced grading definition page + # Confirm that frequently used comments and marking guide options specified during registration are retained + Then I should see "Comment 1" + And I should see "Comment 2" + And the field "Show guide definition to students" matches value "1" + And the field "Show marks per criterion to students" matches value "0" From 9254b7e75a6bd467a3e9fec26900668e51f93a94 Mon Sep 17 00:00:00 2001 From: Pol Torrent i Soler Date: Mon, 22 Apr 2024 16:31:49 +0200 Subject: [PATCH 05/44] MDL-81622 core: fix str_contains usage in cookie_helper --- lib/classes/session/utility/cookie_helper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/classes/session/utility/cookie_helper.php b/lib/classes/session/utility/cookie_helper.php index 75f2eb6433bfd..6e47d1813b739 100644 --- a/lib/classes/session/utility/cookie_helper.php +++ b/lib/classes/session/utility/cookie_helper.php @@ -147,9 +147,9 @@ private static function cookie_response_header_contains_attribute(string $header bool $casesensitive): bool { if ($casesensitive) { - return str_contains($headerstring, $attribute); + return strpos($headerstring, $attribute) !== false; } - return str_contains(strtolower($headerstring), strtolower($attribute)); + return strpos(strtolower($headerstring), strtolower($attribute)) !== false; } /** From f2e48b39f64e946b6228128f77a971f97ac3e8f0 Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Fri, 5 May 2023 17:40:27 +0800 Subject: [PATCH 06/44] MDL-77445 Behat: Coverage for activity chooser navigation --- course/tests/behat/activity_chooser.feature | 91 ++++++++++++++++++++- 1 file changed, 87 insertions(+), 4 deletions(-) diff --git a/course/tests/behat/activity_chooser.feature b/course/tests/behat/activity_chooser.feature index cf9e5a82c8637..c4f0fe232194f 100644 --- a/course/tests/behat/activity_chooser.feature +++ b/course/tests/behat/activity_chooser.feature @@ -9,11 +9,13 @@ Feature: Display and choose from the available activities in course | username | firstname | lastname | email | | teacher | Teacher | 1 | teacher@example.com | And the following "courses" exist: - | fullname | shortname | format | - | Course | C | topics | + | fullname | shortname | format | startdate | + | Course | C | topics | | + | Course 2 | C2 | weeks | 95713920 | And the following "course enrolments" exist: - | user | course | role | - | teacher | C | editingteacher | + | user | course | role | + | teacher | C | editingteacher | + | teacher | C2 | editingteacher | And the following config values are set as admin: | enablemoodlenet | 0 | tool_moodlenet | And I log in as "teacher" @@ -44,6 +46,12 @@ Feature: Display and choose from the available activities in course When I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue" Then I should see "Assignment" in the "help" "core_course > Activity chooser screen" And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." + # Confirm show summary also works for weekly format course + And I am on "C2" course homepage with editing mode on + And I click on "Add an activity or resource" "button" in the "13 January - 19 January" "section" + And I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue" + And I should see "Assignment" in the "help" "core_course > Activity chooser screen" + And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." Scenario: Hide summary Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" @@ -55,6 +63,15 @@ Feature: Display and choose from the available activities in course And "help" "core_course > Activity chooser screen" should not be visible And "Back" "button" should not exist in the "modules" "core_course > Activity chooser screen" And I should not see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "Add an activity or resource" "dialogue" + # Confirm hide summary also works for weekly format course + And I am on "C2" course homepage with editing mode on + And I click on "Add an activity or resource" "button" in the "13 January - 19 January" "section" + And I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue" + And I click on "Back" "button" in the "help" "core_course > Activity chooser screen" + And "modules" "core_course > Activity chooser screen" should be visible + And "help" "core_course > Activity chooser screen" should not be visible + And "Back" "button" should not exist in the "modules" "core_course > Activity chooser screen" + And I should not see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "Add an activity or resource" "dialogue" Scenario: View recommended activities When I log out @@ -189,3 +206,69 @@ Feature: Display and choose from the available activities in course Then I should not see "All" in the "Add an activity or resource" "dialogue" And I should see "Activities" in the "Add an activity or resource" "dialogue" And I should see "Resources" in the "Add an activity or resource" "dialogue" + + Scenario: Teacher can navigate through activity chooser in Topics format course + When I click on "Add an activity or resource" "button" in the "Topic 1" "section" + Then I should see "All" in the "Add an activity or resource" "dialogue" + And I press the tab key + And I press the tab key + And I press the tab key + And I press the tab key + # Confirm right key works + And I press the right key + And I press the right key + And the focused element is "Chat" "menuitem" in the "Add an activity or resource" "dialogue" + # Confirm left key works + And I press the left key + And the focused element is "Book" "menuitem" in the "Add an activity or resource" "dialogue" + # Confirm clicking "x" button closes modal + And I click on "Close" "button" in the "Add an activity or resource" "dialogue" + And "Add an activity or resource" "dialogue" should not be visible + And I click on "Add an activity or resource" "button" in the "Topic 1" "section" + # Confirm escape key closes the modal + And I press the escape key + And "Add an activity or resource" "dialogue" should not be visible + + Scenario: Teacher can navigate through activity chooser in Weekly format course + Given I am on "C2" course homepage with editing mode on + When I click on "Add an activity or resource" "button" in the "13 January - 19 January" "section" + Then I should see "All" in the "Add an activity or resource" "dialogue" + And I press the tab key + And I press the tab key + And I press the tab key + And I press the tab key + # Confirm right key works + And I press the right key + And I press the right key + And the focused element is "Chat" "menuitem" in the "Add an activity or resource" "dialogue" + # Confirm left key works + And I press the left key + And the focused element is "Book" "menuitem" in the "Add an activity or resource" "dialogue" + # Confirm clicking "x" button closes modal + And I click on "Close" "button" in the "Add an activity or resource" "dialogue" + And "Add an activity or resource" "dialogue" should not be visible + And I click on "Add an activity or resource" "button" in the "13 January - 19 January" "section" + # Confirm escape key closes the modal + And I press the escape key + And "Add an activity or resource" "dialogue" should not be visible + + Scenario: Teacher can access 'More help' from activity information in activity chooser + Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" + When I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue" + # Confirm more help link exists + Then "More help" "link" should exist + # Confirm that corresponding help icon exist + And ".iconhelp" "css_element" should exist + # Confirm that link opens in new window + And "Opens in new window" "link" should be visible + # Confirm the same behaviour for weekly format course + And I am on "C2" course homepage with editing mode on + And I click on "Add an activity or resource" "button" in the "13 January - 19 January" "section" + And I should see "All" in the "Add an activity or resource" "dialogue" + And I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue" + # Confirm more help link exists + And "More help" "link" should exist + # Confirm that corresponding help icon exist + And ".iconhelp" "css_element" should exist + # Confirm that link opens in new window + And "Opens in new window" "link" should be visible From 0837a24c055de416911bcf126fc762c4ee101b0f Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Thu, 27 Apr 2023 17:39:57 +0800 Subject: [PATCH 07/44] MDL-77998 Behat: Add behat coverage for preferences navigation --- my/tests/behat/preferences_navigation.feature | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 my/tests/behat/preferences_navigation.feature diff --git a/my/tests/behat/preferences_navigation.feature b/my/tests/behat/preferences_navigation.feature new file mode 100644 index 0000000000000..102b8971a9559 --- /dev/null +++ b/my/tests/behat/preferences_navigation.feature @@ -0,0 +1,106 @@ +@core @core_my @javascript +Feature: Navigate and use preferences page + In order to navigate through preferences page + As a user + I need to be able to use preferences page + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | student1 | Sam | Student | s1@example.com | + And the following "courses" exist: + | fullname | shortname | format | + | Course 1 | C1 | topics | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + And I log in as "admin" + + Scenario Outline: Navigating through user menu Preferences + When I follow "Preferences" in the user menu + # Click each link in the 'Preferences' page. + And I click on "" "link" in the "#page-content" "css_element" + # Confirm that each redirected page has 'Preferences' in the breadcrumbs. + And "Users" "link" should not exist in the ".breadcrumb" "css_element" + Then "Preferences" "link" should exist in the ".breadcrumb" "css_element" + # Additional confirmation that breadcrumbs is correct. + And "" "text" should exist in the ".breadcrumb" "css_element" + # Confirm that user name and profile picture are displayed in header section. + And I should see "Admin User" in the ".page-header-headings" "css_element" + And ".page-header-image" "css_element" should exist in the "#page-header" "css_element" + + Examples: + | userprefpage | + | Edit profile | + | Change password | + | Preferred language | + | Forum preferences | + | Editor preferences | + | Calendar preferences | + | Content bank preferences | + | Message preferences | + | Notification preferences | + | Manage badges | + | Badge preferences | + | Backpack settings | + | This user's role assignments | + | Permissions | + | Check permissions | + | Blog preferences | + | External blogs | + | Register an external blog | + + Scenario Outline: Navigating through course participant preferences + Given I am on "Course 1" course homepage + And I navigate to course participants + And I follow "Sam Student" + When I click on "Preferences" "link" in the "#region-main-box" "css_element" + Then I should see "Sam Student" in the ".page-header-headings" "css_element" + And ".page-header-image" "css_element" should exist in the "#page-header" "css_element" + # Click each link in the 'Preferences' page. + And I click on "" "link" in the "#page-content" "css_element" + # Confirm that each redirected page has 'Users/{user}/Preferences' in the breadcrumbs. + Then "Users" "link" should exist in the ".breadcrumb" "css_element" + And "Sam Student" "link" should exist in the ".breadcrumb" "css_element" + And "Preferences" "link" should exist in the ".breadcrumb" "css_element" + # Additional confirmation that breadcrumbs is correct. + And "" "text" should exist in the ".breadcrumb" "css_element" + # Confirm that user name and profile picture are displayed in header section. + And I should see "Sam Student" in the ".page-header-headings" "css_element" + And ".page-header-image" "css_element" should exist in the "#page-header" "css_element" + + Examples: + | courseprefpage | + | Edit profile | + | Preferred language | + | Forum preferences | + | Editor preferences | + | Calendar preferences | + | Content bank preferences | + | Message preferences | + | Notification preferences | + | This user's role assignments | + | Permissions | + | Check permissions | + + Scenario: Navigation with Event monitoring enabled + Given I navigate to "Reports > Event monitoring rules" in site administration + And I click on "Enable" "link" + And I press "Add a new rule" + And I set the following fields to these values: + | Rule name | Testing1 | + | Area to monitor | Subsystem (core) | + | Event | Allow role override | + And I press "Save changes" + When I follow "Preferences" in the user menu + # Confirm that Event monitoring is visible and clickable. + Then I should see "Miscellaneous" + And I follow "Event monitoring" + # Confirm that user can subscribe to new rule. + And "Subscribe to rule \"Testing1\"" "link" should exist + And I am on "Course 1" course homepage + And I navigate to course participants + And I follow "Sam Student" + And I click on "Preferences" "link" in the "#region-main-box" "css_element" + # Confirm that admin cannot change student's event monitor subscription. + And I should not see "Event monitoring" From e57e5603855d3c55a9b131c97723975bca6f32e8 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Wed, 24 Apr 2024 20:52:32 +0100 Subject: [PATCH 08/44] MDL-81632 block_recentlyaccesseditems: deterministic item ordering. Behat tests of this functionality could trigger the unlikely (in real world usage) scenario where a user has an identical "timeaccess" value for multiple course activities. This led to random failures in said tests when the DB ordered items in apparently random order, where the "timeaccess" value was equal. --- blocks/recentlyaccesseditems/classes/helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/recentlyaccesseditems/classes/helper.php b/blocks/recentlyaccesseditems/classes/helper.php index d1ac05b74baad..61e215ca261a3 100644 --- a/blocks/recentlyaccesseditems/classes/helper.php +++ b/blocks/recentlyaccesseditems/classes/helper.php @@ -58,7 +58,7 @@ public static function get_recent_items(int $limit = 0) { FROM {block_recentlyaccesseditems} rai JOIN {course} c ON c.id = rai.courseid WHERE userid = :userid - ORDER BY rai.timeaccess DESC"; + ORDER BY rai.timeaccess DESC, rai.id DESC"; $records = $DB->get_records_sql($sql, $paramsql); $order = 0; From d9043b324dfbd9d281519cded9001c85f64d8a49 Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Thu, 25 Apr 2024 10:31:58 +0700 Subject: [PATCH 09/44] MDL-77998 Behat: Fix Behat failure --- my/tests/behat/preferences_navigation.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/my/tests/behat/preferences_navigation.feature b/my/tests/behat/preferences_navigation.feature index 102b8971a9559..c7e8637572dc9 100644 --- a/my/tests/behat/preferences_navigation.feature +++ b/my/tests/behat/preferences_navigation.feature @@ -89,7 +89,7 @@ Feature: Navigate and use preferences page And I press "Add a new rule" And I set the following fields to these values: | Rule name | Testing1 | - | Area to monitor | Subsystem (core) | + | Area to monitor | Core | | Event | Allow role override | And I press "Save changes" When I follow "Preferences" in the user menu From eba83406da650a9b3ab8bd16c817ea89bcacdb2d Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Thu, 25 Apr 2024 15:43:45 +0200 Subject: [PATCH 10/44] weekly release 4.1.10+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index 5323e9ec16621..1a1bd54153bff 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112810.00; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112810.01; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.10 (Build: 20240422)'; // Human-friendly version name +$release = '4.1.10+ (Build: 20240425)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From 5b33398f0706ec256d7951d4cfc37f5dc29b754f Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Fri, 26 Apr 2024 15:08:33 +0100 Subject: [PATCH 11/44] MDL-79149 report_participation: more resilient activity access test. Use the "Forum" module rather than the "Book" module, because the latter emits two events when a student views it - if these two events happened either side of a single second, then the report would count two distinct views (when it was trying to assert only one). --- .../tests/behat/filter_participation.feature | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/report/participation/tests/behat/filter_participation.feature b/report/participation/tests/behat/filter_participation.feature index 2d2027347feb5..01c90cbecf273 100644 --- a/report/participation/tests/behat/filter_participation.feature +++ b/report/participation/tests/behat/filter_participation.feature @@ -20,13 +20,9 @@ Feature: In a participation report, admin can filter student actions | student1 | C1 | student | And the following "activity" exists: | course | C1 | - | activity | book | - | name | Test book name | - | idnumber | book1 | - And the following "mod_book > chapter" exists: - | book | Test book name | - | title | Test chapter | - | content | Test chapter content | + | activity | forum | + | name | Test forum name | + | idnumber | forum1 | @javascript Scenario: Filter participation report when only legacy log reader is enabled @@ -37,15 +33,18 @@ Feature: In a participation report, admin can filter student actions And the following config values are set as admin: | loglegacy | 1 | logstore_legacy | - And I am on the "Test book name" "book activity" page logged in as student1 + And I am on the "Test forum name" "forum activity" page logged in as student1 When I am on the "Course 1" course page logged in as admin When I navigate to "Reports" in current page administration And I click on "Course participation" "link" - And I set the field "instanceid" to "Test book name" - And I set the field "roleid" to "Student" + And I set the following fields to these values: + | Activity module | Test forum name | + | Show only | Student | And I press "Go" - Then I should see "Yes (1)" + Then the following should exist in the "reporttable" table: + | -1- | All actions | + | Student 1 | Yes (1) | @javascript Scenario: Filter participation report when standard log reader is enabled later @@ -56,34 +55,39 @@ Feature: In a participation report, admin can filter student actions And the following config values are set as admin: | loglegacy | 1 | logstore_legacy | - And I am on the "Test book name" "book activity" page logged in as student1 + And I am on the "Test forum name" "forum activity" page logged in as student1 And I log out And I log in as "admin" And I navigate to "Plugins > Logging > Manage log stores" in site administration And I click on "Enable" "link" in the "Standard log" "table_row" - And I am on the "Test book name" "book activity" page logged in as student1 + And I am on the "Test forum name" "forum activity" page logged in as student1 And I am on the "Course 1" course page logged in as admin When I navigate to "Reports" in current page administration And I click on "Course participation" "link" - And I set the field "instanceid" to "Test book name" - And I set the field "roleid" to "Student" + And I set the following fields to these values: + | Activity module | Test forum name | + | Show only | Student | And I press "Go" - Then I should see "Yes (2)" + Then the following should exist in the "reporttable" table: + | -1- | All actions | + | Student 1 | Yes (2) | @javascript Scenario: Filter participation report when only standard log reader is enabled by default - Given I am on the "Test book name" "book activity" page logged in as student1 - - And I am on the "Course 1" course page logged in as admin + Given I am on the "Test forum name" "forum activity" page logged in as student1 + When I am on the "Course 1" course page logged in as admin And I navigate to "Reports" in current page administration And I click on "Course participation" "link" - And I set the field "instanceid" to "Test book name" - And I set the field "roleid" to "Student" + And I set the following fields to these values: + | Activity module | Test forum name | + | Show only | Student | And I press "Go" - Then I should see "Yes (1)" + Then the following should exist in the "reporttable" table: + | -1- | All actions | + | Student 1 | Yes (1) | @javascript Scenario Outline: Filter participation report by viewable roles @@ -93,8 +97,8 @@ Feature: In a participation report, admin can filter student actions # Teacher role cannot see Manager by default. Then "Manager" "option" should not exist in the "Show only" "select" And I set the following fields to these values: - | Activity module | Test book name | - | Show only | | + | Activity module | Test forum name | + | Show only | | And I press "Go" And I should see "" in the "reporttable" "table" And I should not see "" in the "reporttable" "table" From 457f0e9384b58d64821d2a8c9c5a8bea6e26eb36 Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Thu, 26 Oct 2023 14:54:37 +0800 Subject: [PATCH 12/44] MDL-79851 mod_h5pactivity: Behat for h5p activity access control --- .../behat/h5pactivity_availability.feature | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 mod/h5pactivity/tests/behat/h5pactivity_availability.feature diff --git a/mod/h5pactivity/tests/behat/h5pactivity_availability.feature b/mod/h5pactivity/tests/behat/h5pactivity_availability.feature new file mode 100644 index 0000000000000..b627a3a40d43e --- /dev/null +++ b/mod/h5pactivity/tests/behat/h5pactivity_availability.feature @@ -0,0 +1,41 @@ +@mod @mod_h5pactivity @core_5hp +Feature: Control H5P activity availability for students + In order to restrict student access to H5P activity + As a teacher + I need to control the availability of the H5P activity + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | 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 | + And the following "activities" exist: + | activity | course | name | + | h5pactivity | C1 | H5P Test | + + @javascript + Scenario Outline: Restrict H5P activity access by date + Given I am on the "H5P Test" "h5pactivity activity editing" page logged in as teacher1 + And I expand all fieldsets + And I click on "Add restriction..." "button" + And I click on "Date" "button" in the "Add restriction..." "dialogue" + And I set the following fields to these values: + | Direction | from | + | x[day] | 1 | + | x[month] | 1 | + | x[year] | | + And I press "Save and return to course" + When I am on the "Course 1" course page logged in as student1 + Then I see "Available from" + + Examples: + | year | fromvisibility | + | ## -1 year ## %Y ## | should not | + | ## +1 year ## %Y ## | should | From 49be0febdc55a5f3b20eb0fe0295e338bfb9022f Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Fri, 12 Jan 2024 17:05:52 +0800 Subject: [PATCH 13/44] MDL-80589 qtype_numerical: Behat to add, edit, preview numeric question --- .../type/numerical/tests/behat/add.feature | 24 ++++++++++- .../type/numerical/tests/behat/edit.feature | 29 +++++++++++++ .../numerical/tests/behat/preview.feature | 42 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/question/type/numerical/tests/behat/add.feature b/question/type/numerical/tests/behat/add.feature index 6444da83c6aca..140b2d136658d 100644 --- a/question/type/numerical/tests/behat/add.feature +++ b/question/type/numerical/tests/behat/add.feature @@ -37,7 +37,7 @@ Feature: Test creating a Numerical question Then I should see "Numerical-001" @javascript - Scenario: Create a Numerical question with units + Scenario: Create a Numerical question with required units When I am on the "Course 1" "core_question > course question bank" page logged in as teacher And I add a "Numerical" question filling the form with: | Question name | Numerical-002 | @@ -70,3 +70,25 @@ Feature: Test creating a Numerical question | id_unitgradingtypes | as a fraction (0-1) of the question grade | | id_multichoicedisplay | a drop-down menu | | id_unitsleft | on the right, for example 1.00cm or 1.00km | + + @javascript + Scenario: Create a Numerical question with optional units + When I am on the "Course 1" "core_question > course question bank" page logged in as teacher + # Add the numerical question with optional units. + And I add a "Numerical" question filling the form with: + | Question name | Numerical Question 1 | + | Question text | What is the sum of $8 + $9? | + | Default mark | 1 | + | General feedback | The correct answer is $17 | + | id_answer_0 | 17 | + | id_tolerance_0 | 0 | + | id_fraction_0 | 100% | + | id_answer_1 | * | + | id_tolerance_1 | 0 | + | id_fraction_1 | 0% | + | id_unitrole | Units are optional. If a unit is entered, it is used to convert the response to Unit 1 before grading. | + | id_unitsleft | on the left, for example $1.00 or £1.00 | + | id_unit_0 | $ | + | id_multiplier_0 | 1 | + # Confirm that the numerical question with optional units is added successfully. + Then I should see "Numerical Question 1" diff --git a/question/type/numerical/tests/behat/edit.feature b/question/type/numerical/tests/behat/edit.feature index a70da10c6ed0c..e1c97c18f1121 100644 --- a/question/type/numerical/tests/behat/edit.feature +++ b/question/type/numerical/tests/behat/edit.feature @@ -55,3 +55,32 @@ Feature: Test editing a Numerical question Then the following fields match these values: | id_answer_0 | 0.00000123456789 | | id_tolerance_1 | 0.0000123456789 | + + Scenario: Edit a Numerical question with optional units + When I am on the "Numerical for editing" "core_question > edit" page logged in as teacher + # Edit the existing numerical question, add in the optional units. + And I set the following fields to these values: + | Question text | What's $500 in Php? aaa | + | Default mark | 1 | + | General feedback | The correct answer is Php27950 aaa | + | id_unitrole | Units are optional. If a unit is entered, it is used to convert the response to Unit 1 before grading. | + | id_unitsleft | on the left, for example $1.00 or £1.00 | + | id_answer_0 | 27950 | + | id_tolerance_0 | 0 | + | id_fraction_0 | 100% | + | id_unit_0 | Php | + | id_multiplier_0 | 55.9 | + And I press "submitbutton" + And I choose "Edit question" action for "Numerical for editing" in the question bank + # Confirm that the numerical question is updated accordingly. + Then the following fields match these values: + | Question text | What's $500 in Php? aaa | + | Default mark | 1 | + | General feedback | The correct answer is Php27950 aaa | + | id_unitrole | Units are optional. If a unit is entered, it is used to convert the response to Unit 1 before grading. | + | id_unitsleft | on the left, for example $1.00 or £1.00 | + | id_answer_0 | 27950 | + | id_tolerance_0 | 0 | + | id_fraction_0 | 100% | + | id_unit_0 | Php | + | id_multiplier_0 | 55.9 | diff --git a/question/type/numerical/tests/behat/preview.feature b/question/type/numerical/tests/behat/preview.feature index d6c595a9a9f56..8d919c3fc2b8e 100644 --- a/question/type/numerical/tests/behat/preview.feature +++ b/question/type/numerical/tests/behat/preview.feature @@ -45,3 +45,45 @@ Feature: Preview a Numerical question And I press "Check" And I should see "Very good." And I should see "Mark 1#00 out of 1#00" + + Scenario: Preview a Numerical question with optional units + Given I am on the "Course 1" "core_question > course question bank" page logged in as teacher + # Add the numerical question with optional units + And I add a "Numerical" question filling the form with: + | Question name | Numerical 1 | + | Question text | What's $500 in Php? img1 | + | Default mark | 1 | + | General feedback | The correct answer is Php27950 img1 | + | id_answer_0 | 27950 | + | id_tolerance_0 | 0 | + | id_fraction_0 | 100% | + | id_feedback_0 | Correct! | + | id_answer_1 | * | + | id_tolerance_1 | 0 | + | id_fraction_1 | 0% | + | id_feedback_1 | Wrong! | + | id_unitrole | Units are optional. If a unit is entered, it is used to convert the response to Unit 1 before grading. | + | id_unitsleft | on the left, for example $1.00 or £1.00 | + | id_unit_0 | Php | + | id_multiplier_0 | 1 | + # Confirm numerical question can be previewed. + When I am on the "Numerical 1" "core_question > preview" page logged in as teacher + Then I should see "Numerical 1" + And I should see "What's $500 in Php?" + # Answer question correctly. + And I set the following fields to these values: + | Answer | 27950 | + And I press "Submit and finish" + # Confirm that corresponding feedback is displayed. + And I should see "The correct answer is Php27950" + And I should see "Correct!" + And "//img[contains(@src, 'gd-logo.png')]" "xpath_element" should exist + And I press "Start again" + # Answer question incorrectly. + And I set the following fields to these values: + | Answer | 27961 | + And I press "Submit and finish" + # Confirm that corresponding feedback is displayed. + And I should see "The correct answer is Php27950" + And I should see "Wrong!" + And "//img[contains(@src, 'gd-logo.png')]" "xpath_element" should exist From 025065daf5533a459411914c2961a9c73f005c1b Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Thu, 2 May 2024 12:28:25 +0800 Subject: [PATCH 14/44] MDL-80589 behat: fixes and improvements to new tests --- .../type/numerical/tests/behat/add.feature | 40 +++++++------ .../type/numerical/tests/behat/edit.feature | 48 +++++++-------- .../numerical/tests/behat/preview.feature | 58 ++++++------------- 3 files changed, 66 insertions(+), 80 deletions(-) diff --git a/question/type/numerical/tests/behat/add.feature b/question/type/numerical/tests/behat/add.feature index 140b2d136658d..5318ad02136ec 100644 --- a/question/type/numerical/tests/behat/add.feature +++ b/question/type/numerical/tests/behat/add.feature @@ -71,24 +71,30 @@ Feature: Test creating a Numerical question | id_multichoicedisplay | a drop-down menu | | id_unitsleft | on the right, for example 1.00cm or 1.00km | - @javascript Scenario: Create a Numerical question with optional units - When I am on the "Course 1" "core_question > course question bank" page logged in as teacher + Given I am on the "Course 1" "core_question > course question bank" page logged in as teacher # Add the numerical question with optional units. And I add a "Numerical" question filling the form with: - | Question name | Numerical Question 1 | - | Question text | What is the sum of $8 + $9? | - | Default mark | 1 | - | General feedback | The correct answer is $17 | - | id_answer_0 | 17 | - | id_tolerance_0 | 0 | - | id_fraction_0 | 100% | - | id_answer_1 | * | - | id_tolerance_1 | 0 | - | id_fraction_1 | 0% | - | id_unitrole | Units are optional. If a unit is entered, it is used to convert the response to Unit 1 before grading. | - | id_unitsleft | on the left, for example $1.00 or £1.00 | - | id_unit_0 | $ | - | id_multiplier_0 | 1 | + | Question name | Numerical Question (optional) | + | Question text | How many meter is 1m + 20cm + 50mm? | + | Default mark | 1 | + | General feedback | The correct answer is 1.25m | + | id_answer_0 | 1.25 | + | id_tolerance_0 | 0 | + | id_fraction_0 | 100% | + | id_answer_1 | 125 | + | id_tolerance_1 | 0 | + | id_fraction_1 | 0% | + | id_unitrole | Units are optional. | + | id_unitsleft | on the right, for example 1.00cm or 1.00km | + | id_unit_0 | m | # Confirm that the numerical question with optional units is added successfully. - Then I should see "Numerical Question 1" + When I choose "Edit question" action for "Numerical Question (optional)" in the question bank + Then the following fields match these values: + | Question name | Numerical Question (optional) | + | Question text | How many meter is 1m + 20cm + 50mm? | + | Default mark | 1 | + | General feedback | The correct answer is 1.25m | + | id_unitrole | Units are optional. If a unit is entered, it is used to convert the response to Unit 1 before grading. | + | id_unitsleft | on the right, for example 1.00cm or 1.00km | + | id_unit_0 | m | diff --git a/question/type/numerical/tests/behat/edit.feature b/question/type/numerical/tests/behat/edit.feature index e1c97c18f1121..63de4496cebb2 100644 --- a/question/type/numerical/tests/behat/edit.feature +++ b/question/type/numerical/tests/behat/edit.feature @@ -57,30 +57,30 @@ Feature: Test editing a Numerical question | id_tolerance_1 | 0.0000123456789 | Scenario: Edit a Numerical question with optional units - When I am on the "Numerical for editing" "core_question > edit" page logged in as teacher - # Edit the existing numerical question, add in the optional units. + Given I am on the "Numerical for editing" "core_question > edit" page logged in as teacher + # Edit the existing numerical question, changing the unit to optional. And I set the following fields to these values: - | Question text | What's $500 in Php? aaa | - | Default mark | 1 | - | General feedback | The correct answer is Php27950 aaa | - | id_unitrole | Units are optional. If a unit is entered, it is used to convert the response to Unit 1 before grading. | - | id_unitsleft | on the left, for example $1.00 or £1.00 | - | id_answer_0 | 27950 | - | id_tolerance_0 | 0 | - | id_fraction_0 | 100% | - | id_unit_0 | Php | - | id_multiplier_0 | 55.9 | + | Question name | Numerical Question (optional) | + | Question text | How many meter is 1m + 20cm + 50mm? | + | Default mark | 1 | + | General feedback | The correct answer is 1.25m | + | id_answer_0 | 1.25 | + | id_tolerance_0 | 0 | + | id_fraction_0 | 100% | + | id_answer_1 | 125 | + | id_tolerance_1 | 0 | + | id_fraction_1 | 0% | + | id_unitrole | Units are optional. | + | id_unitsleft | on the right, for example 1.00cm or 1.00km | + | id_unit_0 | m | And I press "submitbutton" - And I choose "Edit question" action for "Numerical for editing" in the question bank - # Confirm that the numerical question is updated accordingly. + When I choose "Edit question" action for "Numerical Question (optional)" in the question bank + # Confirm that the numerical question with optional unit is updated accordingly. Then the following fields match these values: - | Question text | What's $500 in Php? aaa | - | Default mark | 1 | - | General feedback | The correct answer is Php27950 aaa | - | id_unitrole | Units are optional. If a unit is entered, it is used to convert the response to Unit 1 before grading. | - | id_unitsleft | on the left, for example $1.00 or £1.00 | - | id_answer_0 | 27950 | - | id_tolerance_0 | 0 | - | id_fraction_0 | 100% | - | id_unit_0 | Php | - | id_multiplier_0 | 55.9 | + | Question name | Numerical Question (optional) | + | Question text | How many meter is 1m + 20cm + 50mm? | + | Default mark | 1 | + | General feedback | The correct answer is 1.25m | + | id_unitrole | Units are optional. If a unit is entered, it is used to convert the response to Unit 1 before grading. | + | id_unitsleft | on the right, for example 1.00cm or 1.00km | + | id_unit_0 | m | diff --git a/question/type/numerical/tests/behat/preview.feature b/question/type/numerical/tests/behat/preview.feature index 8d919c3fc2b8e..4b4b1ce0f3d99 100644 --- a/question/type/numerical/tests/behat/preview.feature +++ b/question/type/numerical/tests/behat/preview.feature @@ -47,43 +47,23 @@ Feature: Preview a Numerical question And I should see "Mark 1#00 out of 1#00" Scenario: Preview a Numerical question with optional units - Given I am on the "Course 1" "core_question > course question bank" page logged in as teacher - # Add the numerical question with optional units - And I add a "Numerical" question filling the form with: - | Question name | Numerical 1 | - | Question text | What's $500 in Php? img1 | - | Default mark | 1 | - | General feedback | The correct answer is Php27950 img1 | - | id_answer_0 | 27950 | - | id_tolerance_0 | 0 | - | id_fraction_0 | 100% | - | id_feedback_0 | Correct! | - | id_answer_1 | * | - | id_tolerance_1 | 0 | - | id_fraction_1 | 0% | - | id_feedback_1 | Wrong! | - | id_unitrole | Units are optional. If a unit is entered, it is used to convert the response to Unit 1 before grading. | - | id_unitsleft | on the left, for example $1.00 or £1.00 | - | id_unit_0 | Php | - | id_multiplier_0 | 1 | - # Confirm numerical question can be previewed. - When I am on the "Numerical 1" "core_question > preview" page logged in as teacher - Then I should see "Numerical 1" - And I should see "What's $500 in Php?" - # Answer question correctly. + Given I am on the "Numerical-001" "core_question > edit" page logged in as teacher + # Edit the existing numerical question, add in the optional units. And I set the following fields to these values: - | Answer | 27950 | - And I press "Submit and finish" - # Confirm that corresponding feedback is displayed. - And I should see "The correct answer is Php27950" - And I should see "Correct!" - And "//img[contains(@src, 'gd-logo.png')]" "xpath_element" should exist - And I press "Start again" - # Answer question incorrectly. - And I set the following fields to these values: - | Answer | 27961 | - And I press "Submit and finish" - # Confirm that corresponding feedback is displayed. - And I should see "The correct answer is Php27950" - And I should see "Wrong!" - And "//img[contains(@src, 'gd-logo.png')]" "xpath_element" should exist + | Question name | Numerical Question (optional) | + | Question text | How many meter is 1m + 20cm + 50mm? | + | Default mark | 1 | + | General feedback | The correct answer is 1.25m | + | id_answer_0 | 1.25 | + | id_tolerance_0 | 0 | + | id_fraction_0 | 100% | + | id_answer_1 | 125 | + | id_tolerance_1 | 0 | + | id_fraction_1 | 0% | + | id_unitrole | Units are optional. | + | id_unitsleft | on the right, for example 1.00cm or 1.00km | + | id_unit_0 | m | + And I press "submitbutton" + When I choose "Preview" action for "Numerical Question (optional)" in the question bank + # Unit is optional, so the unit select box should not be exist. + Then "Select one unit" "select" should not exist From 48aa9aae566805a02730addf89a3671be17d0b13 Mon Sep 17 00:00:00 2001 From: AMOS bot Date: Fri, 3 May 2024 00:10:35 +0000 Subject: [PATCH 15/44] Automatically generated installer lang files --- install/lang/bg/error.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/install/lang/bg/error.php b/install/lang/bg/error.php index 485dd25fe1028..bc1e612f32bec 100644 --- a/install/lang/bg/error.php +++ b/install/lang/bg/error.php @@ -39,4 +39,7 @@ $string['cannotfindcomponent'] = 'Не можа да намери компонент'; $string['cannotunzipfile'] = 'Файлът не може да се разархивира'; $string['componentisuptodate'] = 'Компонентът е актуален'; -$string['remotedownloaderror'] = 'Изтеглянето на компонента към вашия сървър пропадна, проверете настройките на proxy, препоръчително е PHP разширението cURL.

Вие трябва ръчно да изтеглите файла {$a->url}, да го копирате в директория {$a->dest} на вашия сървър и да го разархивирате там.'; +$string['remotedownloaderror'] = '

Изтеглянето на компонента към вашия сървър пропадна, проверете настройките на proxy; препоръчително е PHP разширението cURL.

Вие трябва ръчно да изтеглите файла {$a->url}, да го копирате в директория "{$a->dest}" на вашия сървър и да го разархивирате там.

'; +$string['wrongdestpath'] = 'Грешен път към целта'; +$string['wrongsourcebase'] = 'Грешен изходен адрес'; +$string['wrongzipfilename'] = 'Грешно име на ZIP файл-а'; From 85825dcc55b35248dfe447406644248c98973081 Mon Sep 17 00:00:00 2001 From: Ilya Tregubov Date: Fri, 3 May 2024 08:22:54 +0800 Subject: [PATCH 16/44] weekly release 4.1.10+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index 1a1bd54153bff..1e61d64dceb41 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112810.01; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112810.02; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.10+ (Build: 20240425)'; // Human-friendly version name +$release = '4.1.10+ (Build: 20240503)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From d393030c588e0fe96db634d4b7e38ad843774f0e Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Wed, 18 Oct 2023 10:27:51 +0800 Subject: [PATCH 17/44] MDL-79702 core_tag: Behat for activity tags deletion during course reset --- .../behat/activity_tags_deletion.feature | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 course/tests/behat/activity_tags_deletion.feature diff --git a/course/tests/behat/activity_tags_deletion.feature b/course/tests/behat/activity_tags_deletion.feature new file mode 100644 index 0000000000000..e89e3f81aa220 --- /dev/null +++ b/course/tests/behat/activity_tags_deletion.feature @@ -0,0 +1,121 @@ +@core @core_course @core_tag @javascript +Feature: Delete activity tags during course reset + As an admin, + I should be able to delete activity tags by performing course reset + + Background: + Given the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "activities" exist: + | activity | name | course | + | book | Test Book | C1 | + | forum | Test Forum | C1 | + | glossary | Test Glossary | C1 | + + Scenario: Delete book chapter tags using course reset + # Added multiple tags to confirm that all tags are deleted on course reset. + Given the following "mod_book > chapters" exist: + | book | title | content | tags | + | Test Book | Chapter 1 | Chapter 1 content | SampleTag, ChapterTag | + # Perform course reset without checking anything. + And I log in as "admin" + And I am on the "Course 1" "reset" page + And I press "Reset" + And I press "Continue" + # Confirm that book chapter tags are not deleted. + When I am on the "Test Book" "book activity" page + Then I should see "SampleTag" + And I should see "ChapterTag" + # Delete book chapter tags using course reset. + And I am on the "Course 1" "reset" page + And I expand all fieldsets + And I click on "Remove all book tags" "checkbox" + And I press "Reset" + # Confirm that book chapter tags are sucessfully deleted. + And I should see "Book tags have been deleted" in the "Books" "table_row" + And I press "Continue" + And I am on the "Test Book" "book activity" page + And I should not see "SampleTag" + And I should not see "ChapterTag" + + Scenario Outline: Delete forum discussion tags using course reset + Given the following "mod_forum > discussions" exist: + | user | forum | name | message | + | admin | Test Forum | Discussion 1 | Discussion 1 message | + # Added multiple tags to confirm that all tags are deleted on course reset. + And I am on the "Test Forum" "forum activity" page logged in as admin + And I follow "Discussion 1" + And I click on "Edit" "link" + And I set the following fields to these values: + | Tags | SampleTag, DiscussionTag | + And I press "Save changes" + # Perform course reset without checking anything. + And I am on the "Course 1" "reset" page + And I press "Reset" + And I press "Continue" + # Confirm that forum discussion tags are not deleted. + When I am on the "Test Forum" "forum activity" page + And I follow "Discussion 1" + Then I should see "SampleTag" + And I should see "DiscussionTag" + And I am on the "Course 1" "reset" page + And I expand all fieldsets + # Depending on value, either delete all discussion posts or remove all forum discussion tags only. + And I click on "" "checkbox" + # Confirm `Remove all forum tags` is disabled when `Delete all posts` on previous step is checked. + And the "Remove all forum tags" "checkbox" should be + And I press "Reset" + And I should see "" in the "Forums" "table_row" + And I press "Continue" + And I am on the "Test Forum" "forum activity" page + # Confirm discussion is deleted when `Delete all posts` was checked. + And I see "There are no discussion topics yet in this forum" + # Confirm all discussion tags are deleted. + And I should not see "SampleTag" + And I should not see "DiscussionTag" + + Examples: + | resetcheck | resetmessage | canbechecked | forumview | + | Delete all posts | Delete all posts | disabled | should | + | Remove all forum tags | Forum tags have been deleted | enabled | should not | + + Scenario Outline: Delete glossary entry tags using course reuse + Given the following "mod_glossary > entries" exist: + | glossary | concept | definition | user | + | Test Glossary | Aubergine | Also eggpgplant | admin | + # Added multiple tags to confirm that all tags are deleted on course reset. + And I am on the "Test Glossary" "glossary activity" page logged in as admin + And I click on "Edit entry: Aubergine" "link" + And I expand all fieldsets + And I set the following fields to these values: + | Tags | SampleTag, GlossaryTag | + And I press "Save changes" + # Perform course reset without checking anything. + And I am on the "Course 1" "reset" page + And I press "Reset" + And I press "Continue" + # Confirm that glossary entry tags are not deleted. + When I am on the "Test Glossary" "glossary activity" page + Then I should see "SampleTag" + And I should see "GlossaryTag" + And I am on the "Course 1" "reset" page + And I expand all fieldsets + # Depending on value, either delete all glossary entries or remove all glossary entry tags only. + And I click on "" "checkbox" + # Confirm `Remove all forum tags` is disabled when `Delete entries from all glossaries` on previous step is checked. + And the "Remove all glossary tags" "checkbox" should be + And I press "Reset" + And I should see "" in the "Glossaries" "table_row" + And I press "Continue" + And I am on the "Test Glossary" "glossary activity" page + # Confirm glossary entries are deleted when `Delete entries from all glossaries` is checked. + And I see "No entries found in this section" + # Confirm that glossary entry tags are deleted. + And I should not see "SampleTag" + And I should not see "GlossaryTag" + + Examples: + | resetcheck | resetmessage | canbechecked | glossaryview | + | Delete entries from all glossaries | Delete entries from all glossaries | disabled | should | + | Remove all glossary tags | Glossary tags have been deleted | enabled | should not | From 72c30389255e88f9318aa6f3da32713767d33052 Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Mon, 6 May 2024 16:39:44 +0800 Subject: [PATCH 18/44] MDL-79702 behat: improvements and fixes to new tests This commit adds the handling of tags as string separated by comma to forum and glossary modules. --- .../behat/activity_tags_deletion.feature | 41 +++++++------------ mod/forum/lib.php | 5 ++- mod/glossary/tests/generator/lib.php | 12 +++++- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/course/tests/behat/activity_tags_deletion.feature b/course/tests/behat/activity_tags_deletion.feature index e89e3f81aa220..3d829af588403 100644 --- a/course/tests/behat/activity_tags_deletion.feature +++ b/course/tests/behat/activity_tags_deletion.feature @@ -1,4 +1,4 @@ -@core @core_course @core_tag @javascript +@core @core_course @core_tag Feature: Delete activity tags during course reset As an admin, I should be able to delete activity tags by performing course reset @@ -8,11 +8,12 @@ Feature: Delete activity tags during course reset | fullname | shortname | | Course 1 | C1 | And the following "activities" exist: - | activity | name | course | - | book | Test Book | C1 | - | forum | Test Forum | C1 | - | glossary | Test Glossary | C1 | + | activity | name | course | idnumber | + | book | Test Book | C1 | book1 | + | forum | Test Forum | C1 | forum1 | + | glossary | Test Glossary | C1 | glossary1 | + @javascript Scenario: Delete book chapter tags using course reset # Added multiple tags to confirm that all tags are deleted on course reset. Given the following "mod_book > chapters" exist: @@ -32,26 +33,20 @@ Feature: Delete activity tags during course reset And I expand all fieldsets And I click on "Remove all book tags" "checkbox" And I press "Reset" - # Confirm that book chapter tags are sucessfully deleted. + # Confirm that book chapter tags are deleted. And I should see "Book tags have been deleted" in the "Books" "table_row" And I press "Continue" And I am on the "Test Book" "book activity" page And I should not see "SampleTag" And I should not see "ChapterTag" + @javascript Scenario Outline: Delete forum discussion tags using course reset Given the following "mod_forum > discussions" exist: - | user | forum | name | message | - | admin | Test Forum | Discussion 1 | Discussion 1 message | - # Added multiple tags to confirm that all tags are deleted on course reset. - And I am on the "Test Forum" "forum activity" page logged in as admin - And I follow "Discussion 1" - And I click on "Edit" "link" - And I set the following fields to these values: - | Tags | SampleTag, DiscussionTag | - And I press "Save changes" + | user | forum | name | message | tags | + | admin | forum1 | Discussion 1 | Discussion 1 message | SampleTag, DiscussionTag | # Perform course reset without checking anything. - And I am on the "Course 1" "reset" page + And I am on the "Course 1" "reset" page logged in as admin And I press "Reset" And I press "Continue" # Confirm that forum discussion tags are not deleted. @@ -80,19 +75,13 @@ Feature: Delete activity tags during course reset | Delete all posts | Delete all posts | disabled | should | | Remove all forum tags | Forum tags have been deleted | enabled | should not | + @javascript Scenario Outline: Delete glossary entry tags using course reuse Given the following "mod_glossary > entries" exist: - | glossary | concept | definition | user | - | Test Glossary | Aubergine | Also eggpgplant | admin | - # Added multiple tags to confirm that all tags are deleted on course reset. - And I am on the "Test Glossary" "glossary activity" page logged in as admin - And I click on "Edit entry: Aubergine" "link" - And I expand all fieldsets - And I set the following fields to these values: - | Tags | SampleTag, GlossaryTag | - And I press "Save changes" + | glossary | concept | definition | user | tags | + | Test Glossary | Aubergine | Also eggpgplant | admin | SampleTag, GlossaryTag | # Perform course reset without checking anything. - And I am on the "Course 1" "reset" page + And I am on the "Course 1" "reset" page logged in as admin And I press "Reset" And I press "Continue" # Confirm that glossary entry tags are not deleted. diff --git a/mod/forum/lib.php b/mod/forum/lib.php index aa2f40b3db57e..da61386c23729 100644 --- a/mod/forum/lib.php +++ b/mod/forum/lib.php @@ -3272,7 +3272,10 @@ function forum_add_discussion($discussion, $mform=null, $unused=null, $userid=nu } if (isset($discussion->tags)) { - core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, context_module::instance($cm->id), $discussion->tags); + $tags = is_array($discussion->tags) ? $discussion->tags : explode(',', $discussion->tags); + + core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, + context_module::instance($cm->id), $tags); } if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) { diff --git a/mod/glossary/tests/generator/lib.php b/mod/glossary/tests/generator/lib.php index 56debf62ccaeb..67a3be40ebbc5 100644 --- a/mod/glossary/tests/generator/lib.php +++ b/mod/glossary/tests/generator/lib.php @@ -188,6 +188,16 @@ public function create_entry(array $data): stdClass { $DB->insert_record('glossary_entries_categories', ['entryid' => $id, 'categoryid' => $categoryid]); } - return $DB->get_record('glossary_entries', array('id' => $id), '*', MUST_EXIST); + $entries = $DB->get_record('glossary_entries', ['id' => $id], '*', MUST_EXIST); + + if (isset($record['tags'])) { + $cm = get_coursemodule_from_instance('glossary', $glossary->id); + $tags = is_array($record['tags']) ? $record['tags'] : explode(',', $record['tags']); + + core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $id, + context_module::instance($cm->id), $tags); + } + + return $entries; } } From f687e02decf52f6026f98e8bafe3f24b35d8bb85 Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Tue, 7 May 2024 09:04:00 +0800 Subject: [PATCH 19/44] MDL-81805 behat: remove unnecessary @javascript from tests --- .../tests/behat/course_report_participation.feature | 1 - .../tests/behat/message_participants.feature | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/report/participation/tests/behat/course_report_participation.feature b/report/participation/tests/behat/course_report_participation.feature index d9fca6ae486e2..2042af30d8454 100644 --- a/report/participation/tests/behat/course_report_participation.feature +++ b/report/participation/tests/behat/course_report_participation.feature @@ -16,7 +16,6 @@ Feature: In a course administration page, navigate through report page, test for | admin | C1 | editingteacher | | student1 | C1 | student | - @javascript Scenario: Selector should be available in the course participation page Given I log in as "admin" And I am on "Course 1" course homepage diff --git a/report/participation/tests/behat/message_participants.feature b/report/participation/tests/behat/message_participants.feature index 91781aff29d06..7b34abcf5d197 100644 --- a/report/participation/tests/behat/message_participants.feature +++ b/report/participation/tests/behat/message_participants.feature @@ -1,5 +1,5 @@ -@report @report_participation @javascript -Feature: Use the particiaption report to message groups of students +@report @report_participation +Feature: Use the participation report to message groups of students In order to engage with students based on participation As a teacher I need to be able to message students who have not participated in an activity @@ -28,6 +28,7 @@ Feature: Use the particiaption report to message groups of students | idnumber | book1 | And I am on the "Test book name" "book activity" page logged in as student1 + @javascript Scenario: Message all students from the participation report Given I am on the "Course 1" course page logged in as teacher1 And I navigate to "Reports" in current page administration @@ -42,6 +43,7 @@ Feature: Use the particiaption report to message groups of students And I press "Send message to 3 people" And I should see "Message sent to 3 people" + @javascript Scenario: Message students who have not participated in book Given I am on the "Course 1" course page logged in as teacher1 And I navigate to "Reports" in current page administration @@ -59,7 +61,7 @@ Feature: Use the particiaption report to message groups of students And I press "Send message to 2 people" And I should see "Message sent to 2 people" - Scenario: Ensure no message options when messaging is disabled + Scenario: When messaging is disabled no message options should be displayed Given the following config values are set as admin: | messaging | 0 | And I am on the "Course 1" course page logged in as teacher1 From 234d81e39bc97f75f2ca1966a3f4b864f76e1df0 Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Thu, 21 Dec 2023 11:24:29 +0800 Subject: [PATCH 20/44] MDL-80453 mod_glossary: Behat for different glossary format display --- .../behat/glossary_display_formats.feature | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 mod/glossary/tests/behat/glossary_display_formats.feature diff --git a/mod/glossary/tests/behat/glossary_display_formats.feature b/mod/glossary/tests/behat/glossary_display_formats.feature new file mode 100644 index 0000000000000..2a0f0ae764df0 --- /dev/null +++ b/mod/glossary/tests/behat/glossary_display_formats.feature @@ -0,0 +1,99 @@ +@mod @mod_glossary +Feature: Glossary can be set to various display formats + In order to display different glossary formats + As a teacher + I can set the glossary activity display format + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | One | teacher1@example.com | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + Given the following "activities" exist: + | activity | course | name | + | glossary | C1 | Glossary 1 | + And the following "mod_glossary > entries" exist: + | glossary | concept | definition | + | Glossary 1 | Entry 1 | Entry 1 definition | + | Glossary 1 | Entry 2 | Entry 2 definition | + + Scenario: Glossary display format is entry list style + Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1 + And I set the following fields to these values: + | displayformat | entrylist | + When I press "Save and display" + # Confirm that glossary display format is entry list. + # In this format, the concept definitions are not displayed. + Then I should not see "by Admin User" + And I should not see "Entry 1 definition" + And I should not see "Entry 2 definition" + And ".entrylist" "css_element" should exist + + Scenario: Glossary display format is FAQ-style + Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1 + And I set the following fields to these values: + | displayformat | faq | + When I press "Save and display" + # Confirm that glossary format is FAQ. + # In this format, the words Question and Answer are displayed. + Then I should see "Question:" + And I should see "Answer:" + And ".faq" "css_element" should exist + + @_file_upload @javascript + Scenario: Glossary display format is full without author style + Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1 + And I set the following fields to these values: + | displayformat | fullwithoutauthor | + And I press "Save and display" + And I press "Add entry" + # Add an entry with an attachment. + And I set the following fields to these values: + | Concept | Entry 3 | + | Definition | Entry 3 definition | + | Attachment | lib/tests/fixtures/gd-logo.png | + When I press "Save changes" + # Confirm that glossary format is full without author style. + # In this format, the image link should exist and author's name should not be visible. + Then "gd-logo.png" "link" should exist + And I should not see "by Admin User" + And ".fullwithoutauthor" "css_element" should exist + + @_file_upload @javascript + Scenario: Glossary display format is encyclopedia style + Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1 + And I set the following fields to these values: + | displayformat | encyclopedia | + And I press "Save and display" + And I press "Add entry" + # Add an entry with an attachment. + And I set the following fields to these values: + | Concept | Entry 3 | + | Definition | Entry 3 definition | + | Attachment | lib/tests/fixtures/gd-logo.png | + When I press "Save changes" + # Confirm that glossary format is encyclopedia. + # In this format, the image element should be displayed. + Then "//img[contains(@src, 'gd-logo.png')]" "xpath_element" should exist + And ".encyclopedia" "css_element" should exist + + Scenario Outline: Glossary display format can be set to dictionary, continuous and full with author + Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1 + # Assign the corresponding display format to glossary activity. + And I set the following fields to these values: + | displayformat | | + When I press "Save and display" + # Confirm that glossary format is the display format set in the previous step. + Then I should "by Admin User" + And "." "css_element" should exist + + Examples: + | display_format | visibility | + | dictionary | not see | + | continuous | not see | + | fullwithauthor | see | From c2193308a6d05e548329e257c5b763a05723bf60 Mon Sep 17 00:00:00 2001 From: "info@eWallah.net" Date: Wed, 8 May 2024 21:48:40 +0200 Subject: [PATCH 21/44] MDL-80972 behat: Behat check_server_status can provide better info. --- lib/behat/classes/util.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/behat/classes/util.php b/lib/behat/classes/util.php index 3481ff2f43290..409051c328c62 100644 --- a/lib/behat/classes/util.php +++ b/lib/behat/classes/util.php @@ -226,7 +226,7 @@ public static function check_server_status() { behat_error (BEHAT_EXITCODE_REQUIREMENT, $CFG->behat_wwwroot . ' is not available, ensure you specified ' . 'correct url and that the server is set up and started.' . PHP_EOL . ' More info in ' . - behat_command::DOCS_URL . PHP_EOL); + behat_command::DOCS_URL . PHP_EOL . parent::get_site_info()); } // Check if cli version is same as web version. From b35f70ce14e2b779550a3c6d16e42557b804b3c2 Mon Sep 17 00:00:00 2001 From: Ilya Tregubov Date: Fri, 10 May 2024 09:09:52 +0800 Subject: [PATCH 22/44] weekly release 4.1.10+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index 1e61d64dceb41..85c320110df1f 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112810.02; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112810.03; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.10+ (Build: 20240503)'; // Human-friendly version name +$release = '4.1.10+ (Build: 20240510)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From 7f3617860c92b8555a15bb48c08fbfa65ddb325c Mon Sep 17 00:00:00 2001 From: AMOS bot Date: Sun, 12 May 2024 00:14:21 +0000 Subject: [PATCH 23/44] Automatically generated installer lang files --- install/lang/uz/admin.php | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 install/lang/uz/admin.php diff --git a/install/lang/uz/admin.php b/install/lang/uz/admin.php new file mode 100644 index 0000000000000..b1405e1d3255f --- /dev/null +++ b/install/lang/uz/admin.php @@ -0,0 +1,34 @@ +. + +/** + * 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['cliincorrectvalueretry'] = 'Noto\'g\'ri qiymat, iltimos qayta urinib ko\'ring'; +$string['clitypevalue'] = 'qiymatni kiritish'; +$string['clitypevaluedefault'] = 'qiymatni kiriting, ({$a}) odatiy qiymatni ishlatish uchun Enter tumgasini bosing'; From cf48f359ad54a2834b6a1bbcedb9c3de37d827f9 Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Wed, 25 Oct 2023 18:00:32 +0800 Subject: [PATCH 24/44] MDL-79838 mod_h5pactivity: Behat for H5P activity grade settings control --- .../behat/h5pactivity_grade_settings.feature | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 mod/h5pactivity/tests/behat/h5pactivity_grade_settings.feature diff --git a/mod/h5pactivity/tests/behat/h5pactivity_grade_settings.feature b/mod/h5pactivity/tests/behat/h5pactivity_grade_settings.feature new file mode 100644 index 0000000000000..ad3ae04c6b303 --- /dev/null +++ b/mod/h5pactivity/tests/behat/h5pactivity_grade_settings.feature @@ -0,0 +1,100 @@ +@mod @mod_h5pactivity @core_h5p +Feature: Teacher can control h5p activity grading setting + In order to set h5p activity grade + As a teacher + I need to be able to control h5p activity grading setting + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | + | student2 | Student | 2 | student2@example.com | + | student3 | Student | 3 | student3@example.com | + | student4 | Student | 4 | student4@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 | + | student2 | C1 | student | + | student3 | C1 | student | + | student4 | C1 | student | + + @javascript + Scenario: Verify that invalid grades given to students are not saved + Given the following "activities" exist: + | activity | name | course | + | h5pactivity | H5P point | C1 | + # Activity grade settings are not saved using generators so manual setting is necessary. + And I am on the "H5P point" "h5pactivity activity editing" page logged in as teacher1 + And I set the following fields to these values: + | grade[modgrade_type] | Point | + | grade[modgrade_point] | 10 | + And I press "Save and return to course" + And I navigate to "View > Grader report" in the course gradebook + And I turn editing mode on + When I give the grade "50" to the user "Student 1" for the grade item "H5P point" + And I press "Save changes" + And I turn editing mode off + # Confirm that grades are not saved when grade entered is > maximum grade. + Then the following should exist in the "user-grades" table: + | -1- | -1- | -4- | + | Student 1 | student1@example.com | - | + | Student 2 | student2@example.com | - | + | Student 3 | student3@example.com | - | + | Student 4 | student4@example.com | - | + + @javascript + Scenario: Verify that valid grades given to students are saved + Given the following "activities" exist: + | activity | name | course | + | h5pactivity | H5P point | C1 | + # Activity grade settings are not saved using generators so manual setting is necessary. + And I am on the "H5P point" "h5pactivity activity editing" page logged in as teacher1 + And I set the following fields to these values: + | grade[modgrade_type] | Point | + | grade[modgrade_point] | 10 | + And I press "Save and return to course" + And I navigate to "View > Grader report" in the course gradebook + And I turn editing mode on + When I give the grade "10" to the user "Student 1" for the grade item "H5P point" + And I give the grade "5" to the user "Student 2" for the grade item "H5P point" + And I give the grade "0" to the user "Student 3" for the grade item "H5P point" + And I press "Save changes" + And I turn editing mode off + # Confirm that corresponding grades are stored for each student. + And the following should exist in the "user-grades" table: + | -1- | -1- | -4- | + | Student 1 | student1@example.com | 10 | + | Student 2 | student2@example.com | 5 | + | Student 3 | student3@example.com | 0 | + | Student 4 | student4@example.com | - | + + @javascript + Scenario: Verify that scales given to students are saved + Given the following "activities" exist: + | activity | name | course | + | h5pactivity | H5P scale | C1 | + # Activity grade settings are not saved using generators so manual setting is necessary. + And I am on the "H5P scale" "h5pactivity activity editing" page logged in as teacher1 + And I set the following fields to these values: + | grade[modgrade_type] | Scale | + | grade[modgrade_scale] | Default competence scale | + And I press "Save and return to course" + And I navigate to "View > Grader report" in the course gradebook + And I turn editing mode on + And I give the grade "Not yet competent" to the user "Student 1" for the grade item "H5P scale" + And I give the grade "Competent" to the user "Student 2" for the grade item "H5P scale" + And I give the grade "Competent" to the user "Student 4" for the grade item "H5P scale" + When I press "Save changes" + And I turn editing mode off + # Confirm that scale set for student is successfully saved. + Then the following should exist in the "user-grades" table: + | -1- | -1- | -4- | + | Student 1 | student1@example.com | Not yet competent | + | Student 2 | student2@example.com | Competent | + | Student 3 | student3@example.com | - | + | Student 4 | student4@example.com | Competent | From f7da739ea04a3d4bd0f0c335e048af1a76d2cbaf Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Thu, 16 May 2024 18:25:49 +0700 Subject: [PATCH 25/44] weekly release 4.1.10+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index 85c320110df1f..09745f7bf6f84 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112810.03; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112810.04; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.10+ (Build: 20240510)'; // Human-friendly version name +$release = '4.1.10+ (Build: 20240516)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From a48f00d9402677782d79303e19ccedc8144ead6f Mon Sep 17 00:00:00 2001 From: Jordan Tomkinson Date: Tue, 14 May 2024 10:30:13 +0100 Subject: [PATCH 26/44] MDL-81751 ddl: new reserved keywords in Aurora MySQL database engine --- lib/ddl/mysql_sql_generator.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ddl/mysql_sql_generator.php b/lib/ddl/mysql_sql_generator.php index def4c20ed8be2..429387cb1b3bc 100644 --- a/lib/ddl/mysql_sql_generator.php +++ b/lib/ddl/mysql_sql_generator.php @@ -636,7 +636,10 @@ public static function getReservedWords() { '_filename', 'admin', 'cume_dist', 'dense_rank', 'empty', 'except', 'first_value', 'grouping', 'groups', 'json_table', 'lag', 'last_value', 'lead', 'nth_value', 'ntile', 'of', 'over', 'percent_rank', 'persist', 'persist_only', 'rank', 'recursive', 'row_number', - 'system', 'window' + 'system', 'window', + // Added in Amazon Aurora MySQL version 3.06.0: + // https://docs.aws.amazon.com/AmazonRDS/latest/AuroraMySQLReleaseNotes/AuroraMySQL.Updates.3060.html . + 'accept', 'aws_bedrock_invoke_model', 'aws_sagemaker_invoke_endpoint', 'content_type', 'timeout_ms', ); return $reserved_words; } From 31b68a2b5d5f0eee17c3329a9ec9ff5a96f9ade8 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Tue, 14 May 2024 13:20:25 +0200 Subject: [PATCH 27/44] MDL-81897 tool_mobile: Force partitioned cookies on inapp browser --- admin/tool/mobile/launch.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/admin/tool/mobile/launch.php b/admin/tool/mobile/launch.php index 64d3ee8ac4c2c..51145cf0f1400 100644 --- a/admin/tool/mobile/launch.php +++ b/admin/tool/mobile/launch.php @@ -69,6 +69,11 @@ throw new moodle_exception('servicenotavailable', 'webservice'); } +// If the user is using the inapp (embedded) browser, we need to set the Secure and Partitioned attributes to the session cookie. +if (\core_useragent::is_moodle_app()) { + \core\session\utility\cookie_helper::add_attributes_to_cookie_response_header('MoodleSession'.$CFG->sessioncookie, ['Secure', 'Partitioned']); +} + require_login(0, false); // Require an active user: not guest, not suspended. From 5425fb3d791da1236648c8f011ea9241af5d4510 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 28 May 2024 11:40:25 +0800 Subject: [PATCH 28/44] MDL-81897 tool_mobile: Coding style fix --- admin/tool/mobile/launch.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/admin/tool/mobile/launch.php b/admin/tool/mobile/launch.php index 51145cf0f1400..cbe9a97bf24d9 100644 --- a/admin/tool/mobile/launch.php +++ b/admin/tool/mobile/launch.php @@ -71,7 +71,10 @@ // If the user is using the inapp (embedded) browser, we need to set the Secure and Partitioned attributes to the session cookie. if (\core_useragent::is_moodle_app()) { - \core\session\utility\cookie_helper::add_attributes_to_cookie_response_header('MoodleSession'.$CFG->sessioncookie, ['Secure', 'Partitioned']); + \core\session\utility\cookie_helper::add_attributes_to_cookie_response_header( + "MoodleSession{$CFG->sessioncookie}", + ['Secure', 'Partitioned'], + ); } require_login(0, false); From a7d476c35fd49bd83fb4e7c38c96a990e3fe77c7 Mon Sep 17 00:00:00 2001 From: Mihail Geshoski Date: Mon, 12 Jun 2023 13:06:13 +0800 Subject: [PATCH 29/44] MDL-77685 core_grades: Fix row_column_of_table_should_contain() Backport from MDL-77632. --- lib/tests/behat/behat_general.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php index a2617263532c6..82e54cf5764fb 100644 --- a/lib/tests/behat/behat_general.php +++ b/lib/tests/behat/behat_general.php @@ -1411,8 +1411,8 @@ public function row_column_of_table_should_contain($row, $column, $table, $value // Check if value exists in specific row/column. // Get row xpath. // GoutteDriver uses DomCrawler\Crawler and it is making XPath relative to the current context, so use descendant. - $rowxpath = $tablexpath."/tbody/tr[descendant::th[normalize-space(.)=" . $rowliteral . - "] | descendant::td[normalize-space(.)=" . $rowliteral . "]]"; + $rowxpath = $tablexpath . "/tbody/tr[descendant::*[@class='rowtitle'][normalize-space(.)=" . $rowliteral . "] | " . " + descendant::th[normalize-space(.)=" . $rowliteral . "] | descendant::td[normalize-space(.)=" . $rowliteral . "]]"; $columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.)," . $valueliteral . ")]"; From f8cce391b2130777231c86573a5810fde43e78b3 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Fri, 22 Mar 2024 15:53:50 +0000 Subject: [PATCH 30/44] MDL-77685 grade: remove Behat specific report rendering hacks. These changes follow the same approach as that taken elsewhere in the Gradebook in 3643f48e0f. --- grade/report/singleview/classes/local/screen/user.php | 11 +++-------- grade/report/user/classes/report/user.php | 10 ++-------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/grade/report/singleview/classes/local/screen/user.php b/grade/report/singleview/classes/local/screen/user.php index 12740e299c983..33f41d42d1326 100644 --- a/grade/report/singleview/classes/local/screen/user.php +++ b/grade/report/singleview/classes/local/screen/user.php @@ -187,14 +187,9 @@ public function format_line($item): array { $itemicon = html_writer::div($this->format_icon($item), 'mr-1'); $itemtype = \html_writer::span($this->structure->get_element_type_string($gradetreeitem), 'd-block text-uppercase small dimmed_text'); - // If a behat test site is running avoid outputting the information about the type of the grade item. - // This additional information currently causes issues in behat particularly with the existing xpath used to - // interact with table elements. - if (!defined('BEHAT_SITE_RUNNING')) { - $itemcontent = html_writer::div($itemtype . $itemname); - } else { - $itemcontent = html_writer::div($itemname); - } + + $itemtitle = html_writer::div($itemname, 'rowtitle'); + $itemcontent = html_writer::div($itemtype . $itemtitle); $line = [ html_writer::div($itemicon . $itemcontent . $lockicon, "{$type} d-flex align-items-center"), diff --git a/grade/report/user/classes/report/user.php b/grade/report/user/classes/report/user.php index e27d667ce2b03..1eb4ece975762 100644 --- a/grade/report/user/classes/report/user.php +++ b/grade/report/user/classes/report/user.php @@ -556,14 +556,8 @@ private function fill_table_recursive(array &$element) { } // Generate the content for a cell that represents a grade item. - // If a behat test site is running avoid outputting the information about the type of the grade item. - // This additional information causes issues in behat particularly with the existing xpath used to - // interact with table elements. - if (!defined('BEHAT_SITE_RUNNING')) { - $content = \html_writer::div($itemtype . $fullname); - } else { - $content = \html_writer::div($fullname); - } + $itemtitle = \html_writer::div($fullname, 'rowtitle'); + $content = \html_writer::div($itemtype . $itemtitle); // Name. $data['itemname']['content'] = \html_writer::div($itemicon . $content, "{$type} d-flex align-items-center"); From aa230649d96e13fca9f838ef2692581c54ed8e38 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Thu, 30 May 2024 17:00:42 +0200 Subject: [PATCH 31/44] weekly release 4.1.10+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index 09745f7bf6f84..5788c12bfc327 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112810.04; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112810.05; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.10+ (Build: 20240516)'; // Human-friendly version name +$release = '4.1.10+ (Build: 20240530)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From 629a7066be03f88e7f6d7a3db135fe86485ce55d Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Fri, 17 May 2024 10:20:24 +0100 Subject: [PATCH 32/44] MDL-81955 assignfeedback_editpdf: ensure ghostscript present in tests. --- mod/assign/feedback/editpdf/tests/feedback_test.php | 1 + 1 file changed, 1 insertion(+) diff --git a/mod/assign/feedback/editpdf/tests/feedback_test.php b/mod/assign/feedback/editpdf/tests/feedback_test.php index e7bab2a021e58..a3e309c140cc3 100644 --- a/mod/assign/feedback/editpdf/tests/feedback_test.php +++ b/mod/assign/feedback/editpdf/tests/feedback_test.php @@ -396,6 +396,7 @@ public function test_conversion_task() { * and false when not modified. */ public function test_is_feedback_modified() { + $this->require_ghostscript(); $this->resetAfterTest(); $course = $this->getDataGenerator()->create_course(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); From d05795db8eece2943241a29a5443fb4685ba6070 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Thu, 15 Feb 2024 17:26:38 +0100 Subject: [PATCH 33/44] MDL-80959 tool_mobile: Use different user keys for QR and auto login --- admin/tool/mobile/classes/api.php | 4 ++-- admin/tool/mobile/classes/external.php | 4 ++-- admin/tool/mobile/classes/privacy/provider.php | 7 ++++++- admin/tool/mobile/tests/privacy/provider_test.php | 12 +++++++++--- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/admin/tool/mobile/classes/api.php b/admin/tool/mobile/classes/api.php index d127c78bcbf4b..e717050789dac 100644 --- a/admin/tool/mobile/classes/api.php +++ b/admin/tool/mobile/classes/api.php @@ -419,13 +419,13 @@ public static function get_autologin_key() { public static function get_qrlogin_key(stdClass $mobilesettings) { global $USER; // Delete previous keys. - delete_user_key('tool_mobile', $USER->id); + delete_user_key('tool_mobile/qrlogin', $USER->id); // Create a new key. $iprestriction = !empty($mobilesettings->qrsameipcheck) ? getremoteaddr(null) : null; $qrkeyttl = !empty($mobilesettings->qrkeyttl) ? $mobilesettings->qrkeyttl : self::LOGIN_QR_KEY_TTL; $validuntil = time() + $qrkeyttl; - return create_user_key('tool_mobile', $USER->id, null, $iprestriction, $validuntil); + return create_user_key('tool_mobile/qrlogin', $USER->id, null, $iprestriction, $validuntil); } /** diff --git a/admin/tool/mobile/classes/external.php b/admin/tool/mobile/classes/external.php index ed079f36bc057..7b51ee19cb9d6 100644 --- a/admin/tool/mobile/classes/external.php +++ b/admin/tool/mobile/classes/external.php @@ -649,8 +649,8 @@ public static function get_tokens_for_qr_login($qrloginkey, $userid) { api::check_autologin_prerequisites($params['userid']); // Checks https, avoid site admins using this... // Validate and delete the key. - $key = validate_user_key($params['qrloginkey'], 'tool_mobile', null); - delete_user_key('tool_mobile', $params['userid']); + $key = validate_user_key($params['qrloginkey'], 'tool_mobile/qrlogin', null); + delete_user_key('tool_mobile/qrlogin', $params['userid']); // Double check key belong to user. if ($key->userid != $params['userid']) { diff --git a/admin/tool/mobile/classes/privacy/provider.php b/admin/tool/mobile/classes/privacy/provider.php index 54c61a1c2a8de..2a60cc5699bb4 100644 --- a/admin/tool/mobile/classes/privacy/provider.php +++ b/admin/tool/mobile/classes/privacy/provider.php @@ -66,7 +66,7 @@ public static function get_contexts_for_userid(int $userid) : contextlist { FROM {user_private_key} k JOIN {user} u ON k.userid = u.id JOIN {context} ctx ON ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel - WHERE k.userid = :userid AND k.script = 'tool_mobile'"; + WHERE k.userid = :userid AND (k.script = 'tool_mobile' OR k.script = 'tool_mobile/qrlogin')"; $params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER]; $contextlist = new contextlist(); $contextlist->add_from_sql($sql, $params); @@ -88,6 +88,7 @@ public static function get_users_in_context(userlist $userlist) { // Add users based on userkey. \core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'tool_mobile'); + \core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'tool_mobile/qrlogin'); } /** @@ -108,6 +109,7 @@ public static function export_user_data(approved_contextlist $contextlist) { } // Export associated userkeys. \core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile'); + \core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile/qrlogin'); } /** * Export all user preferences for the plugin. @@ -138,6 +140,7 @@ public static function delete_data_for_all_users_in_context(\context $context) { $userid = $context->instanceid; // Delete all the userkeys. \core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid); + \core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid); } /** * Delete all user data for the specified user, in the specified contexts. @@ -158,6 +161,7 @@ public static function delete_data_for_user(approved_contextlist $contextlist) { $userid = $context->instanceid; // Delete all the userkeys. \core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid); + \core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid); } /** @@ -178,5 +182,6 @@ public static function delete_data_for_users(approved_userlist $userlist) { // Delete all the userkeys. \core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid); + \core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid); } } diff --git a/admin/tool/mobile/tests/privacy/provider_test.php b/admin/tool/mobile/tests/privacy/provider_test.php index 6d2fd7d324ddd..f827ce2008c59 100644 --- a/admin/tool/mobile/tests/privacy/provider_test.php +++ b/admin/tool/mobile/tests/privacy/provider_test.php @@ -87,7 +87,8 @@ public function test_get_users_in_context() { $context1 = \context_user::instance($user1->id); $context2 = \context_user::instance($user2->id); $key1 = get_user_key('tool_mobile', $user1->id); - $key2 = get_user_key('tool_mobile', $user2->id); + $key2 = get_user_key('tool_mobile/qrlogin', $user1->id); + $key3 = get_user_key('tool_mobile', $user2->id); // Ensure only user1 is found in context1. $userlist = new \core_privacy\local\request\userlist($context1, $component); @@ -174,12 +175,15 @@ public function test_delete_data_for_users() { $context1 = \context_user::instance($user1->id); $context2 = \context_user::instance($user2->id); $keyvalue1 = get_user_key('tool_mobile', $user1->id); - $keyvalue2 = get_user_key('tool_mobile', $user2->id); + $keyvalue2 = get_user_key('tool_mobile/qrlogin', $user1->id); + $keyvalue3 = get_user_key('tool_mobile', $user2->id); $key1 = $DB->get_record('user_private_key', ['value' => $keyvalue1]); - // Before deletion, we should have 2 user_private_keys. + // Before deletion, we should have 2 user_private_keys for tool_mobile and one for tool_mobile/qrlogin. $count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']); $this->assertEquals(2, $count); + $count = $DB->count_records('user_private_key', ['script' => 'tool_mobile/qrlogin']); + $this->assertEquals(1, $count); // Ensure deleting wrong user in the user context does nothing. $approveduserids = [$user2->id]; @@ -197,6 +201,8 @@ public function test_delete_data_for_users() { // Ensure only user1's data is deleted, user2's remains. $count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']); $this->assertEquals(1, $count); + $count = $DB->count_records('user_private_key', ['script' => 'tool_mobile/qrlogin']); + $this->assertEquals(0, $count); $params = ['script' => $component]; $userid = $DB->get_field_select('user_private_key', 'userid', 'script = :script', $params); From 836b2c23a210317d130017d77bb64e3b510869a9 Mon Sep 17 00:00:00 2001 From: Cameron Ball Date: Fri, 3 May 2024 14:59:03 +0800 Subject: [PATCH 34/44] MDL-81774 curl: Strip auth headers when redirecting to different host --- lib/filelib.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/filelib.php b/lib/filelib.php index 97ed9f2f270da..bfc1ece92a564 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -3845,6 +3845,7 @@ protected function request($url, $options = array()) { $redirects++; + $currenturl = $redirecturl ?? $url; $redirecturl = null; if (isset($this->info['redirect_url'])) { if (preg_match('|^https?://|i', $this->info['redirect_url'])) { @@ -3902,6 +3903,12 @@ protected function request($url, $options = array()) { } curl_setopt($curl, CURLOPT_URL, $redirecturl); + + if (parse_url($currenturl)['host'] !== parse_url($redirecturl)['host']) { + curl_setopt($curl, CURLOPT_HTTPAUTH, null); + curl_setopt($curl, CURLOPT_USERPWD, null); + } + $ret = curl_exec($curl); $this->info = curl_getinfo($curl); From f55b829f4444916377c8e8944b4a2c78d0a35f08 Mon Sep 17 00:00:00 2001 From: lameze Date: Wed, 22 May 2024 08:39:52 +0800 Subject: [PATCH 35/44] MDL-81989 install: remove unnecessary closing div tags --- install.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) mode change 100644 => 100755 install.php diff --git a/install.php b/install.php old mode 100644 new mode 100755 index 82c5e350b86f4..2b9f7a532dd8c --- a/install.php +++ b/install.php @@ -478,7 +478,7 @@ if ($hint_database !== '') { echo '
'.$hint_database.'
'; } - echo ''; + install_print_footer($config); die; } @@ -610,11 +610,8 @@ if ($hint_admindir !== '') { echo '
'.$hint_admindir.'
'; } - echo ''; } - echo ''; - install_print_footer($config); die; } From 28b8fc9896c49eae50cea45620ba66bff9745e88 Mon Sep 17 00:00:00 2001 From: Stevani Andolo Date: Wed, 1 May 2024 20:07:33 +0800 Subject: [PATCH 36/44] MDL-81412 calendar: Sanitise calendar event names --- calendar/lib.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/calendar/lib.php b/calendar/lib.php index f582ea3751d80..ca49081794116 100644 --- a/calendar/lib.php +++ b/calendar/lib.php @@ -3555,6 +3555,18 @@ function ($event) { } } + // Check if $data has events. + if (isset($data->events)) { + // Let's check and sanitize all "name" in $data->events before it's sent to front end. + foreach ($data->events as $d) { + $name = $d->name ?? null; + // Encode special characters if our decoded name does not match the original name. + if ($name && (html_entity_decode($name) !== $name)) { + $d->name = htmlspecialchars(html_entity_decode($name), ENT_QUOTES, 'utf-8'); + } + } + } + return [$data, $template]; } From a10506b8d70609478fef156d489e0c7d727b6098 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Thu, 2 May 2024 22:25:41 +0100 Subject: [PATCH 37/44] MDL-81778 mod_bigbluebuttonbn: access checks when getting meeting URL. --- .../classes/external/get_join_url.php | 6 +++++ .../tests/external/get_join_url_test.php | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/mod/bigbluebuttonbn/classes/external/get_join_url.php b/mod/bigbluebuttonbn/classes/external/get_join_url.php index 8a2b8fe49d4b3..a3432b4d6b318 100644 --- a/mod/bigbluebuttonbn/classes/external/get_join_url.php +++ b/mod/bigbluebuttonbn/classes/external/get_join_url.php @@ -59,6 +59,8 @@ public static function execute_parameters(): external_function_parameters { * @param int $cmid the bigbluebuttonbn course module id * @param null|int $groupid * @return array (empty array for now) + * + * @throws restricted_context_exception */ public static function execute( int $cmid, @@ -85,7 +87,11 @@ public static function execute( } $instance->set_group_id($groupid); + // Validate that the user has access to this activity and to join the meeting. self::validate_context($instance->get_context()); + if (!$instance->can_join()) { + throw new restricted_context_exception(); + } try { $result['join_url'] = meeting::join_meeting($instance); diff --git a/mod/bigbluebuttonbn/tests/external/get_join_url_test.php b/mod/bigbluebuttonbn/tests/external/get_join_url_test.php index 3bdd93242a4f1..c7591ff7502ed 100644 --- a/mod/bigbluebuttonbn/tests/external/get_join_url_test.php +++ b/mod/bigbluebuttonbn/tests/external/get_join_url_test.php @@ -16,7 +16,9 @@ namespace mod_bigbluebuttonbn\external; +use context_course; use external_api; +use restricted_context_exception; use mod_bigbluebuttonbn\instance; use mod_bigbluebuttonbn\local\config; use mod_bigbluebuttonbn\test\testcase_helper_trait; @@ -86,6 +88,28 @@ public function test_execute_without_login() { $this->get_join_url($instance->get_cm_id()); } + /** + * Test execution with a user who doesn't have the capability to join the meeting + */ + public function test_execute_without_capability(): void { + global $DB; + + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $record = $this->getDataGenerator()->create_module('bigbluebuttonbn', ['course' => $course->id]); + $instance = instance::get_from_instanceid($record->id); + + $user = $this->getDataGenerator()->create_and_enrol($course); + $this->setUser($user); + + $student = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST); + assign_capability('mod/bigbluebuttonbn:join', CAP_PROHIBIT, $student, context_course::instance($course->id), true); + + $this->expectException(restricted_context_exception::class); + $this->get_join_url($instance->get_cm_id()); + } + /** * Test execute API CALL with invalid login */ From 57f20b6cb352893871c3afdfa8a4c09a96e16764 Mon Sep 17 00:00:00 2001 From: meirzamoodle Date: Mon, 3 Jun 2024 16:50:03 +0700 Subject: [PATCH 38/44] MDL-81890 tool_moodlenet: Fix sesskey checks --- admin/tool/moodlenet/index.php | 2 +- admin/tool/moodlenet/options.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/tool/moodlenet/index.php b/admin/tool/moodlenet/index.php index 5103730e0528a..f32bec9ab0253 100644 --- a/admin/tool/moodlenet/index.php +++ b/admin/tool/moodlenet/index.php @@ -63,7 +63,7 @@ } redirect($url); } else if ($continue) { - confirm_sesskey(); + require_sesskey(); // Handle backups. if (strtolower($importinfo->get_resource()->get_extension()) == 'mbz') { diff --git a/admin/tool/moodlenet/options.php b/admin/tool/moodlenet/options.php index 2245150439bbc..7454f7ed72bae 100644 --- a/admin/tool/moodlenet/options.php +++ b/admin/tool/moodlenet/options.php @@ -81,7 +81,7 @@ } if ($import && $module) { - confirm_sesskey(); + require_sesskey(); $handlerinfo = $handlerregistry->get_resource_handler_for_mod_and_strategy($importinfo->get_resource(), $module, $strategy); if (is_null($handlerinfo)) { From 137d311fd1354c679b974633512a771e6e0559a1 Mon Sep 17 00:00:00 2001 From: meirzamoodle Date: Mon, 3 Jun 2024 16:50:30 +0700 Subject: [PATCH 39/44] MDL-81890 course: Fix sesskey checks --- course/downloadcontent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/course/downloadcontent.php b/course/downloadcontent.php index 584de2822b5ba..6add496fbd8fa 100644 --- a/course/downloadcontent.php +++ b/course/downloadcontent.php @@ -46,7 +46,7 @@ // If download confirmed, prepare and start the zipstream of the course download content. if ($isdownload) { - confirm_sesskey(); + require_sesskey(); $exportoptions = null; From a0d8c025f732d5c18a2b9d1a8e5cbee35dce86f4 Mon Sep 17 00:00:00 2001 From: meirzamoodle Date: Mon, 3 Jun 2024 16:50:56 +0700 Subject: [PATCH 40/44] MDL-81890 enrol_lti: Fix sesskey checks --- enrol/lti/configure.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/enrol/lti/configure.php b/enrol/lti/configure.php index 59d17780029fb..6617a94870d66 100644 --- a/enrol/lti/configure.php +++ b/enrol/lti/configure.php @@ -39,8 +39,7 @@ global $CFG, $DB, $PAGE, $USER; require_once($CFG->libdir . '/filelib.php'); require_login(null, false); - -confirm_sesskey(); +require_sesskey(); $launchid = required_param('launchid', PARAM_TEXT); $modules = optional_param_array('modules', [], PARAM_INT); $grades = optional_param_array('grades', [], PARAM_INT); From c18b59808cefe7b54c85dce6bf2cc71601080667 Mon Sep 17 00:00:00 2001 From: meirzamoodle Date: Mon, 3 Jun 2024 19:49:45 +0700 Subject: [PATCH 41/44] MDL-81890 assign: confirm_sesskey fixed in view_fix_rescaled_null_grades --- mod/assign/locallib.php | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index e8402e38c751c..3ad5e97b12f1b 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -9500,20 +9500,22 @@ protected function view_fix_rescaled_null_grades() { $confirm = optional_param('confirm', 0, PARAM_BOOL); if ($confirm) { - confirm_sesskey(); - - // Fix the grades. - $this->fix_null_grades(); - unset_config('has_rescaled_null_grades_' . $instance->id, 'assign'); - - // Display the notice. - $o .= $this->get_renderer()->notification(get_string('fixrescalednullgradesdone', 'assign'), 'notifysuccess'); + if (confirm_sesskey()) { + // Fix the grades. + $this->fix_null_grades(); + unset_config('has_rescaled_null_grades_' . $instance->id, 'assign'); + // Display the success notice. + $o .= $this->get_renderer()->notification(get_string('fixrescalednullgradesdone', 'assign'), 'notifysuccess'); + } else { + // If the sesskey is not valid, then display the error notice. + $o .= $this->get_renderer()->notification(get_string('invalidsesskey', 'error'), 'notifyerror'); + } $url = new moodle_url( '/mod/assign/view.php', - array( + [ 'id' => $this->get_course_module()->id, - 'action' => 'grading' - ) + 'action' => 'grading', + ], ); $o .= $this->get_renderer()->continue_button($url); } else { From f878e05d628326fc14432a52b10966e8f4f551f8 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Thu, 6 Jun 2024 22:30:49 +0800 Subject: [PATCH 42/44] NOBUG: Fixed file access permissions --- install.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 install.php diff --git a/install.php b/install.php old mode 100755 new mode 100644 From 5ec01a72267a60df35d2fa4e2cd7956a118c2608 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Thu, 6 Jun 2024 22:30:50 +0800 Subject: [PATCH 43/44] weekly release 4.1.10+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index 5788c12bfc327..aabaf0dfa38f4 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112810.05; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112810.06; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.10+ (Build: 20240530)'; // Human-friendly version name +$release = '4.1.10+ (Build: 20240606)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From c8c84b4af18d50224c17a4a3193e1374fec26625 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Fri, 7 Jun 2024 14:55:32 +0800 Subject: [PATCH 44/44] Moodle release 4.1.11 --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index aabaf0dfa38f4..3b80c712b7615 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112810.06; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112811.00; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.10+ (Build: 20240606)'; // Human-friendly version name +$release = '4.1.11 (Build: 20240610)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level.