���ѧۧݧ�ӧ�� �ާ֧ߧ֧էا֧� - ���֧էѧܧ�ڧ��ӧѧ�� - /home3/cpr76684/public_html/completion.tar
���ѧ٧ѧ�
custom_completion.php 0000644 00000005020 15151264520 0011016 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. declare(strict_types=1); namespace mod_folder\completion; use core_completion\activity_custom_completion; /** * Activity custom completion subclass for the folder resource. * * Class for defining mod_folder's custom completion rules. * * @package mod_folder * @copyright 2022 David Woloszyn <david.woloszyn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class custom_completion extends activity_custom_completion { /** * Fetches the completion state for a given completion rule. * * @param string $rule The completion rule. * @return int The completion state. */ public function get_state(string $rule): int { return COMPLETION_UNKNOWN; } /** * Fetch the list of custom completion rules that this module defines. * * @return array */ public static function get_defined_custom_rules(): array { // This activity/resource does not have any custom rules. return []; } /** * Returns an associative array of the descriptions of custom completion rules. * * @return array */ public function get_custom_rule_descriptions(): array { // This activity/resource does not have any custom rule descriptions. return []; } /** * Show the manual completion button depending on the display option set. * * @return bool */ public function manual_completion_always_shown(): bool { $display = $this->cm->customdata->display ?? null; return ($display == FOLDER_DISPLAY_INLINE ?? false); } /** * Returns an array of all completion rules, in the order they should be displayed to users. * * @return array */ public function get_sort_order(): array { // This module only supports manual completion. return []; } } db/caches.php 0000644 00000002226 15152232433 0007072 0 ustar 00 <?php // This file is part of Moodle - https://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Defined caches used internally by the plugin. * * @package availability_completion * @category cache * @copyright 2020 Ferran Recio <ferran@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $definitions = [ 'previous_cache' => [ 'mode' => cache_store::MODE_REQUEST, 'simplekeys' => true, 'simpledata' => true, 'staticacceleration' => true ], ]; tests/behat/availability_completion.feature 0000644 00000011103 15152232433 0015245 0 ustar 00 @availability @availability_completion Feature: availability_completion In order to control student access to activities As a teacher I need to set completion conditions which prevent student access Background: Given the following "courses" exist: | fullname | shortname | format | enablecompletion | | Course 1 | C1 | topics | 1 | And the following "users" exist: | username | | teacher1 | | student1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | @javascript Scenario: Test condition # Basic setup. Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on # Add a Page with a completion tickbox. And I add a "Page" to section "1" and I fill the form with: | Name | Page 1 | | Description | Test | | Page content | Test | | Completion tracking | 1 | # And another one that depends on it (hidden otherwise). And I add a "Page" to section "2" And I set the following fields to these values: | Name | Page 2 | | Description | Test | | Page content | Test | And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I click on ".availability-item .availability-eye img" "css_element" And I set the field "Activity or resource" to "Page 1" And I press "Save and return to course" # Log back in as student. When I log out And I log in as "student1" And I am on "Course 1" course homepage # Page 2 should not appear yet. Then I should not see "Page 2" in the "region-main" "region" # Mark page 1 complete When I toggle the manual completion state of "Page 1" Then I should see "Page 2" in the "region-main" "region" @javascript Scenario: Test completion and course cache rebuild Given the following "activities" exist: | activity | name | intro | course | idnumber | | forum | forum 1 | forum 1 | C1 | forum1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I open "forum 1" actions menu And I click on "Edit settings" "link" in the "forum 1" activity And I set the following fields to these values: | Completion tracking | Show activity as complete when conditions are met | | completionview | 1 | | completionpostsenabled | 1 | | completionposts | 2 | And I press "Save and return to course" And I add a new discussion to "forum 1" forum with: | Subject | Forum post 1 | | Message | This is the body | And I am on "Course 1" course homepage with editing mode on And I add a "Page" to section "2" And I set the following fields to these values: | Name | Page 2 | | Description | Test | | Page content | Test | And I expand all fieldsets And I press "Add restriction..." And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I click on ".availability-item .availability-eye img" "css_element" And I set the following fields to these values: | Required completion status | must be marked complete | | cm | forum 1 | And I press "Save and return to course" And I log out And I log in as "student1" When I am on "Course 1" course homepage # Page 2 should not appear yet. Then I should not see "Page 2" in the "region-main" "region" And I click on "forum 1" "link" in the "region-main" "region" # Page 2 should not appear yet. And I should not see "Page 2" in the "region-main" "region" And I log out And I log in as "teacher1" And I am on "Course 1" course homepage And I am on the "forum 1" "forum activity editing" page And I expand all fieldsets And I set the following fields to these values: | completionpostsenabled | 0 | And I press "Save and display" And I log out And I log in as "student1" And I am on "Course 1" course homepage And I click on "forum 1" "link" in the "region-main" "region" And I am on "Course 1" course homepage And I should see "Page 2" in the "region-main" "region" tests/behat/availability_completion_previous.feature 0000644 00000024744 15152232433 0017220 0 ustar 00 @availability @availability_completion Feature: Confirm that availability_completion works with previous activity setting In order to control student access to activities As a teacher I need to set completion conditions which prevent student access Background: Given the following "courses" exist: | fullname | shortname | format | enablecompletion | numsections | | Course 1 | C1 | topics | 1 | 5 | And the following "users" exist: | username | | teacher1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | Given the following "activities" exist: | activity | name | intro | course | idnumber | groupmode | completion | section | | page | Page1 | Page 1 description | C1 | page1 | 1 | 1 | 1 | | page | Page Ignored 1 | Page Ignored | C1 | pagei1 | 1 | 0 | 1 | | page | Page2 | Page 2 description | C1 | page2 | 1 | 1 | 3 | | page | Page3 | Page 3 description | C1 | page3 | 1 | 1 | 4 | @javascript Scenario: Test condition with previous activity on an activity Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on # Set Page3 restriction to Previous Activity with completion. When I open "Page3" actions menu And I click on "Edit settings" "link" in the "Page3" activity And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I click on "Displayed if student doesn't meet this condition • Click to hide" "link" And I set the field "Activity or resource" to "Previous activity with completion" And I press "Save and return to course" Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region" When I turn editing mode off Then I should see "Not available unless: The activity Page2 is marked complete" in the "region-main" "region" # Remove Page 2 and check Page3 depends now on Page1. When I turn editing mode on And I change window size to "large" And I delete "Page2" activity And I turn editing mode off Then I should see "Not available unless: The activity Page1 is marked complete" in the "region-main" "region" @javascript Scenario: Test previous activity availability when duplicate an activity Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on # Set Page3 restriction to Previous Activity with completion. When I open "Page3" actions menu And I click on "Edit settings" "link" in the "Page3" activity And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I click on "Displayed if student doesn't meet this condition • Click to hide" "link" And I set the field "Activity or resource" to "Previous activity with completion" And I press "Save and return to course" Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region" When I turn editing mode off Then I should see "Not available unless: The activity Page2 is marked complete" in the "region-main" "region" # Duplicate Page3. When I turn editing mode on And I duplicate "Page3" activity And I turn editing mode off Then I should see "Not available unless: The activity Page3 is marked complete" in the "region-main" "region" @javascript Scenario: Test previous activity availability when modify completion tacking Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on # Set Page3 restriction to Previous Activity with completion. When I open "Page3" actions menu And I click on "Edit settings" "link" in the "Page3" activity And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I click on "Displayed if student doesn't meet this condition • Click to hide" "link" And I set the field "Activity or resource" to "Previous activity with completion" And I press "Save and return to course" Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region" When I turn editing mode off Then I should see "Not available unless: The activity Page2 is marked complete" in the "region-main" "region" # Test if I disable completion tracking on Page2 section 5 depends on Page2. When I turn editing mode on And I change window size to "large" When I open "Page2" actions menu And I click on "Edit settings" "link" in the "Page2" activity And I set the following fields to these values: | Completion tracking | Do not indicate activity completion | And I press "Save and return to course" When I turn editing mode off Then I should see "Not available unless: The activity Page1 is marked complete" in the "region-main" "region" @javascript Scenario: Test condition with previous activity on a section Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on # Set section 4 restriction to Previous Activity with completion. When I edit the section "4" And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I click on "Displayed if student doesn't meet this condition • Click to hide" "link" And I set the field "Activity or resource" to "Previous activity with completion" And I press "Save changes" Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region" When I turn editing mode off Then I should see "Not available unless: The activity Page2 is marked complete" in the "region-main" "region" # Remove Page 2 and check Section 4 depends now on Page1. When I turn editing mode on And I change window size to "large" And I delete "Page2" activity And I turn editing mode off Then I should see "Not available unless: The activity Page1 is marked complete" in the "region-main" "region" @javascript Scenario: Test condition with previous activity on the first activity of the course Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on # Try to set Page1 restriction to Previous Activity with completion. When I open "Page1" actions menu And I click on "Edit settings" "link" in the "Page1" activity And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I click on "Displayed if student doesn't meet this condition • Click to hide" "link" Then the "Activity or resource" select box should not contain "Previous activity with completion" # Set Page2 restriction to Previous Activity with completion and delete Page1. When I am on "Course 1" course homepage When I open "Page2" actions menu And I click on "Edit settings" "link" in the "Page2" activity And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I click on "Displayed if student doesn't meet this condition • Click to hide" "link" And I set the field "Activity or resource" to "Previous activity with completion" And I press "Save and return to course" Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region" # Delete Page 1 and check than Page2 now depends on a missing activity (no previous activity found). When I am on "Course 1" course homepage And I delete "Page1" activity And I turn editing mode off Then I should see "Not available unless: The activity (Missing activity)" in the "region-main" "region" @javascript Scenario: Test previous activities on empty sections Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I change window size to "large" # Set section 2 restriction to Previous Activity with completion. When I edit the section "2" And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I click on "Displayed if student doesn't meet this condition • Click to hide" "link" And I set the field "Activity or resource" to "Previous activity with completion" And I press "Save changes" Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region" When I turn editing mode off Then I should see "Not available unless: The activity Page1 is marked complete" in the "region-main" "region" # Set section 5 restriction to Previous Activity with completion. When I turn editing mode on And I edit the section "5" And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I click on "Displayed if student doesn't meet this condition • Click to hide" "link" And I set the field "Activity or resource" to "Previous activity with completion" And I press "Save changes" Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region" When I turn editing mode off Then I should see "Not available unless: The activity Page3 is marked complete" in the "region-main" "region" # Test if I disable completion tracking on Page3 section 5 depends on Page2. When I turn editing mode on And I open "Page3" actions menu And I click on "Edit settings" "link" in the "Page3" activity And I set the following fields to these values: | Completion tracking | Do not indicate activity completion | And I press "Save and return to course" When I turn editing mode off Then I should see "Not available unless: The activity Page2 is marked complete" in the "region-main" "region" tests/behat/conditional_bug.feature 0000644 00000004715 15152232433 0013515 0 ustar 00 @availability @availability_completion Feature: Confirm that conditions on completion no longer cause a bug In order to use completion conditions As a teacher I need it to not break when I set up certain conditions on some modules Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "users" exist: | username | | teacher1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | @javascript Scenario: Multiple completion conditions on glossary # Set up course. Given I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Settings" in current page administration And I expand all fieldsets And I set the field "Enable completion tracking" to "Yes" And I press "Save and display" And I turn editing mode on # Add a couple of Pages with manual completion. And I add a "Page" to section "1" and I fill the form with: | Name | Page1 | | Page content | x | And I add a "Page" to section "1" and I fill the form with: | Name | Page2 | | Page content | x | # Add a Glossary. When I add a "Glossary" to section "1" And I set the following fields to these values: | Name | TestGlossary | And I expand all fieldsets # Add restrictions to the previous Pages being complete. And I press "Add restriction..." And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I set the field "Activity or resource" to "Page1" And I press "Add restriction..." And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I set the field with xpath "//div[contains(concat(' ', normalize-space(@class), ' '), ' availability-item ')][preceding-sibling::div]//select[@name='cm']" to "Page2" And I press "Save and return to course" Then I should see "Not available unless:" in the ".activity.glossary" "css_element" And I should see "The activity Page1 is marked complete" in the ".activity.glossary" "css_element" And I should see "The activity Page2 is marked complete" in the ".activity.glossary" "css_element" # Behat will automatically check there is no error on this page. And I am on the TestGlossary "glossary activity" page And I should see "TestGlossary" tests/condition_test.php 0000644 00000113773 15152232433 0011460 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace availability_completion; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->libdir . '/completionlib.php'); /** * Unit tests for the completion condition. * * @package availability_completion * @copyright 2014 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class condition_test extends \advanced_testcase { /** * Setup to ensure that fixtures are loaded. */ public static function setupBeforeClass(): void { global $CFG; // Load the mock info class so that it can be used. require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info.php'); require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info_module.php'); require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info_section.php'); } /** * Load required classes. */ public function setUp(): void { condition::wipe_static_cache(); } /** * Tests constructing and using condition as part of tree. */ public function test_in_tree() { global $USER, $CFG; $this->resetAfterTest(); $this->setAdminUser(); // Create course with completion turned on and a Page. $CFG->enablecompletion = true; $CFG->enableavailability = true; $generator = $this->getDataGenerator(); $course = $generator->create_course(['enablecompletion' => 1]); $page = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $selfpage = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $modinfo = get_fast_modinfo($course); $cm = $modinfo->get_cm($page->cmid); $info = new \core_availability\mock_info($course, $USER->id); $structure = (object)[ 'op' => '|', 'show' => true, 'c' => [ (object)[ 'type' => 'completion', 'cm' => (int)$cm->id, 'e' => COMPLETION_COMPLETE ] ] ]; $tree = new \core_availability\tree($structure); // Initial check (user has not completed activity). $result = $tree->check_available(false, $info, true, $USER->id); $this->assertFalse($result->is_available()); // Mark activity complete. $completion = new \completion_info($course); $completion->update_state($cm, COMPLETION_COMPLETE); // Now it's true! $result = $tree->check_available(false, $info, true, $USER->id); $this->assertTrue($result->is_available()); } /** * Tests the constructor including error conditions. Also tests the * string conversion feature (intended for debugging only). */ public function test_constructor() { // No parameters. $structure = new \stdClass(); try { $cond = new condition($structure); $this->fail(); } catch (\coding_exception $e) { $this->assertStringContainsString('Missing or invalid ->cm', $e->getMessage()); } // Invalid $cm. $structure->cm = 'hello'; try { $cond = new condition($structure); $this->fail(); } catch (\coding_exception $e) { $this->assertStringContainsString('Missing or invalid ->cm', $e->getMessage()); } // Missing $e. $structure->cm = 42; try { $cond = new condition($structure); $this->fail(); } catch (\coding_exception $e) { $this->assertStringContainsString('Missing or invalid ->e', $e->getMessage()); } // Invalid $e. $structure->e = 99; try { $cond = new condition($structure); $this->fail(); } catch (\coding_exception $e) { $this->assertStringContainsString('Missing or invalid ->e', $e->getMessage()); } // Successful construct & display with all different expected values. $structure->e = COMPLETION_COMPLETE; $cond = new condition($structure); $this->assertEquals('{completion:cm42 COMPLETE}', (string)$cond); $structure->e = COMPLETION_COMPLETE_PASS; $cond = new condition($structure); $this->assertEquals('{completion:cm42 COMPLETE_PASS}', (string)$cond); $structure->e = COMPLETION_COMPLETE_FAIL; $cond = new condition($structure); $this->assertEquals('{completion:cm42 COMPLETE_FAIL}', (string)$cond); $structure->e = COMPLETION_INCOMPLETE; $cond = new condition($structure); $this->assertEquals('{completion:cm42 INCOMPLETE}', (string)$cond); // Successful contruct with previous activity. $structure->cm = condition::OPTION_PREVIOUS; $cond = new condition($structure); $this->assertEquals('{completion:cmopprevious INCOMPLETE}', (string)$cond); } /** * Tests the save() function. */ public function test_save() { $structure = (object)['cm' => 42, 'e' => COMPLETION_COMPLETE]; $cond = new condition($structure); $structure->type = 'completion'; $this->assertEquals($structure, $cond->save()); } /** * Tests the is_available and get_description functions. */ public function test_usage() { global $CFG, $DB; require_once($CFG->dirroot . '/mod/assign/locallib.php'); $this->resetAfterTest(); // Create course with completion turned on. $CFG->enablecompletion = true; $CFG->enableavailability = true; $generator = $this->getDataGenerator(); $course = $generator->create_course(['enablecompletion' => 1]); $user = $generator->create_user(); $generator->enrol_user($user->id, $course->id); $this->setUser($user); // Create a Page with manual completion for basic checks. $page = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'name' => 'Page!', 'completion' => COMPLETION_TRACKING_MANUAL]); // Create an assignment - we need to have something that can be graded // so as to test the PASS/FAIL states. Set it up to be completed based // on its grade item. $assignrow = $this->getDataGenerator()->create_module('assign', [ 'course' => $course->id, 'name' => 'Assign!', 'completion' => COMPLETION_TRACKING_AUTOMATIC]); $DB->set_field('course_modules', 'completiongradeitemnumber', 0, ['id' => $assignrow->cmid]); // As we manually set the field here, we are going to need to reset the modinfo cache. rebuild_course_cache($course->id, true); $assign = new \assign(\context_module::instance($assignrow->cmid), false, false); // Get basic details. $modinfo = get_fast_modinfo($course); $pagecm = $modinfo->get_cm($page->cmid); $assigncm = $assign->get_course_module(); $info = new \core_availability\mock_info($course, $user->id); // COMPLETE state (false), positive and NOT. $cond = new condition((object)[ 'cm' => (int)$pagecm->id, 'e' => COMPLETION_COMPLETE ]); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression('~Page!.*is marked complete~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); // INCOMPLETE state (true). $cond = new condition((object)[ 'cm' => (int)$pagecm->id, 'e' => COMPLETION_INCOMPLETE ]); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $information = $cond->get_description(false, true, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression('~Page!.*is marked complete~', $information); // Mark page complete. $completion = new \completion_info($course); $completion->update_state($pagecm, COMPLETION_COMPLETE); // COMPLETE state (true). $cond = new condition((object)[ 'cm' => (int)$pagecm->id, 'e' => COMPLETION_COMPLETE ]); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $information = $cond->get_description(false, true, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression('~Page!.*is incomplete~', $information); // INCOMPLETE state (false). $cond = new condition((object)[ 'cm' => (int)$pagecm->id, 'e' => COMPLETION_INCOMPLETE ]); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression('~Page!.*is incomplete~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); // We are going to need the grade item so that we can get pass/fails. $gradeitem = $assign->get_grade_item(); \grade_object::set_properties($gradeitem, ['gradepass' => 50.0]); $gradeitem->update(); // With no grade, it should return true for INCOMPLETE and false for // the other three. $cond = new condition((object)[ 'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE ]); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object)[ 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE ]); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); // Check $information for COMPLETE_PASS and _FAIL as we haven't yet. $cond = new condition((object)[ 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS ]); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression('~Assign!.*is complete and passed~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object)[ 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL ]); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression('~Assign!.*is complete and failed~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); // Change the grade to be complete and failed. self::set_grade($assignrow, $user->id, 40); $cond = new condition((object)[ 'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE ]); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object)[ 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE ]); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object)[ 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS ]); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression('~Assign!.*is complete and passed~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object)[ 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL ]); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $information = $cond->get_description(false, true, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression('~Assign!.*is not complete and failed~', $information); // Now change it to pass. self::set_grade($assignrow, $user->id, 60); $cond = new condition((object)[ 'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE ]); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object)[ 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE ]); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object)[ 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS ]); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $information = $cond->get_description(false, true, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression('~Assign!.*is not complete and passed~', $information); $cond = new condition((object)[ 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL ]); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression('~Assign!.*is complete and failed~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); // Simulate deletion of an activity by using an invalid cmid. These // conditions always fail, regardless of NOT flag or INCOMPLETE. $cond = new condition((object)[ 'cm' => ($assigncm->id + 100), 'e' => COMPLETION_COMPLETE ]); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression('~(Missing activity).*is marked complete~', $information); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object)[ 'cm' => ($assigncm->id + 100), 'e' => COMPLETION_INCOMPLETE ]); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); } /** * Tests the is_available and get_description functions for previous activity option. * * @dataProvider previous_activity_data * @param int $grade the current assign grade (0 for none) * @param int $condition true for complete, false for incomplete * @param string $mark activity to mark as complete * @param string $activity activity name to test * @param bool $result if it must be available or not * @param bool $resultnot if it must be available when the condition is inverted * @param string $description the availabiklity text to check */ public function test_previous_activity(int $grade, int $condition, string $mark, string $activity, bool $result, bool $resultnot, string $description): void { global $CFG, $DB; require_once($CFG->dirroot . '/mod/assign/locallib.php'); $this->resetAfterTest(); // Create course with completion turned on. $CFG->enablecompletion = true; $CFG->enableavailability = true; $generator = $this->getDataGenerator(); $course = $generator->create_course(['enablecompletion' => 1]); $user = $generator->create_user(); $generator->enrol_user($user->id, $course->id); $this->setUser($user); // Page 1 (manual completion). $page1 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'name' => 'Page1!', 'completion' => COMPLETION_TRACKING_MANUAL]); // Page 2 (manual completion). $page2 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'name' => 'Page2!', 'completion' => COMPLETION_TRACKING_MANUAL]); // Page ignored (no completion). $pagenocompletion = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'name' => 'Page ignored!']); // Create an assignment - we need to have something that can be graded // so as to test the PASS/FAIL states. Set it up to be completed based // on its grade item. $assignrow = $this->getDataGenerator()->create_module('assign', [ 'course' => $course->id, 'name' => 'Assign!', 'completion' => COMPLETION_TRACKING_AUTOMATIC ]); $DB->set_field('course_modules', 'completiongradeitemnumber', 0, ['id' => $assignrow->cmid]); $assign = new \assign(\context_module::instance($assignrow->cmid), false, false); // Page 3 (manual completion). $page3 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'name' => 'Page3!', 'completion' => COMPLETION_TRACKING_MANUAL]); // Get basic details. $activities = []; $modinfo = get_fast_modinfo($course); $activities['page1'] = $modinfo->get_cm($page1->cmid); $activities['page2'] = $modinfo->get_cm($page2->cmid); $activities['assign'] = $assign->get_course_module(); $activities['page3'] = $modinfo->get_cm($page3->cmid); $prevvalue = condition::OPTION_PREVIOUS; // Setup gradings and completion. if ($grade) { $gradeitem = $assign->get_grade_item(); \grade_object::set_properties($gradeitem, ['gradepass' => 50.0]); $gradeitem->update(); self::set_grade($assignrow, $user->id, $grade); } if ($mark) { $completion = new \completion_info($course); $completion->update_state($activities[$mark], COMPLETION_COMPLETE); } // Set opprevious WITH non existent previous activity. $info = new \core_availability\mock_info_module($user->id, $activities[$activity]); $cond = new condition((object)[ 'cm' => (int)$prevvalue, 'e' => $condition ]); // Do the checks. $this->assertEquals($result, $cond->is_available(false, $info, true, $user->id)); $this->assertEquals($resultnot, $cond->is_available(true, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression($description, $information); } public function previous_activity_data(): array { // Assign grade, condition, activity to complete, activity to test, result, resultnot, description. return [ 'Missing previous activity complete' => [ 0, COMPLETION_COMPLETE, '', 'page1', false, false, '~Missing activity.*is marked complete~' ], 'Missing previous activity incomplete' => [ 0, COMPLETION_INCOMPLETE, '', 'page1', false, false, '~Missing activity.*is incomplete~' ], 'Previous complete condition with previous activity incompleted' => [ 0, COMPLETION_COMPLETE, '', 'page2', false, true, '~Page1!.*is marked complete~' ], 'Previous incomplete condition with previous activity incompleted' => [ 0, COMPLETION_INCOMPLETE, '', 'page2', true, false, '~Page1!.*is incomplete~' ], 'Previous complete condition with previous activity completed' => [ 0, COMPLETION_COMPLETE, 'page1', 'page2', true, false, '~Page1!.*is marked complete~' ], 'Previous incomplete condition with previous activity completed' => [ 0, COMPLETION_INCOMPLETE, 'page1', 'page2', false, true, '~Page1!.*is incomplete~' ], // Depenging on page pass fail (pages are not gradable). 'Previous complete pass condition with previous no gradable activity incompleted' => [ 0, COMPLETION_COMPLETE_PASS, '', 'page2', false, true, '~Page1!.*is complete and passed~' ], 'Previous complete fail condition with previous no gradable activity incompleted' => [ 0, COMPLETION_COMPLETE_FAIL, '', 'page2', false, true, '~Page1!.*is complete and failed~' ], 'Previous complete pass condition with previous no gradable activity completed' => [ 0, COMPLETION_COMPLETE_PASS, 'page1', 'page2', false, true, '~Page1!.*is complete and passed~' ], 'Previous complete fail condition with previous no gradable activity completed' => [ 0, COMPLETION_COMPLETE_FAIL, 'page1', 'page2', false, true, '~Page1!.*is complete and failed~' ], // There's an page without completion between page2 ans assign. 'Previous complete condition with sibling activity incompleted' => [ 0, COMPLETION_COMPLETE, '', 'assign', false, true, '~Page2!.*is marked complete~' ], 'Previous incomplete condition with sibling activity incompleted' => [ 0, COMPLETION_INCOMPLETE, '', 'assign', true, false, '~Page2!.*is incomplete~' ], 'Previous complete condition with sibling activity completed' => [ 0, COMPLETION_COMPLETE, 'page2', 'assign', true, false, '~Page2!.*is marked complete~' ], 'Previous incomplete condition with sibling activity completed' => [ 0, COMPLETION_INCOMPLETE, 'page2', 'assign', false, true, '~Page2!.*is incomplete~' ], // Depending on assign without grade. 'Previous complete condition with previous without grade' => [ 0, COMPLETION_COMPLETE, '', 'page3', false, true, '~Assign!.*is marked complete~' ], 'Previous incomplete condition with previous without grade' => [ 0, COMPLETION_INCOMPLETE, '', 'page3', true, false, '~Assign!.*is incomplete~' ], 'Previous complete pass condition with previous without grade' => [ 0, COMPLETION_COMPLETE_PASS, '', 'page3', false, true, '~Assign!.*is complete and passed~' ], 'Previous complete fail condition with previous without grade' => [ 0, COMPLETION_COMPLETE_FAIL, '', 'page3', false, true, '~Assign!.*is complete and failed~' ], // Depending on assign with grade. 'Previous complete condition with previous fail grade' => [ 40, COMPLETION_COMPLETE, '', 'page3', true, false, '~Assign!.*is marked complete~' ], 'Previous incomplete condition with previous fail grade' => [ 40, COMPLETION_INCOMPLETE, '', 'page3', false, true, '~Assign!.*is incomplete~' ], 'Previous complete pass condition with previous fail grade' => [ 40, COMPLETION_COMPLETE_PASS, '', 'page3', false, true, '~Assign!.*is complete and passed~' ], 'Previous complete fail condition with previous fail grade' => [ 40, COMPLETION_COMPLETE_FAIL, '', 'page3', true, false, '~Assign!.*is complete and failed~' ], 'Previous complete condition with previous pass grade' => [ 60, COMPLETION_COMPLETE, '', 'page3', true, false, '~Assign!.*is marked complete~' ], 'Previous incomplete condition with previous pass grade' => [ 60, COMPLETION_INCOMPLETE, '', 'page3', false, true, '~Assign!.*is incomplete~' ], 'Previous complete pass condition with previous pass grade' => [ 60, COMPLETION_COMPLETE_PASS, '', 'page3', true, false, '~Assign!.*is complete and passed~' ], 'Previous complete fail condition with previous pass grade' => [ 60, COMPLETION_COMPLETE_FAIL, '', 'page3', false, true, '~Assign!.*is complete and failed~' ], ]; } /** * Tests the is_available and get_description functions for * previous activity option in course sections. * * @dataProvider section_previous_activity_data * @param int $condition condition value * @param bool $mark if Page 1 must be mark as completed * @param string $section section to add the availability * @param bool $result expected result * @param bool $resultnot expected negated result * @param string $description description to match */ public function test_section_previous_activity(int $condition, bool $mark, string $section, bool $result, bool $resultnot, string $description): void { global $CFG, $DB; require_once($CFG->dirroot . '/mod/assign/locallib.php'); $this->resetAfterTest(); // Create course with completion turned on. $CFG->enablecompletion = true; $CFG->enableavailability = true; $generator = $this->getDataGenerator(); $course = $generator->create_course( ['numsections' => 4, 'enablecompletion' => 1], ['createsections' => true]); $user = $generator->create_user(); $generator->enrol_user($user->id, $course->id); $this->setUser($user); // Section 1 - page1 (manual completion). $page1 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'name' => 'Page1!', 'section' => 1, 'completion' => COMPLETION_TRACKING_MANUAL]); // Section 1 - page ignored 1 (no completion). $pagenocompletion1 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course, 'name' => 'Page ignored!', 'section' => 1]); // Section 2 - page ignored 2 (no completion). $pagenocompletion2 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course, 'name' => 'Page ignored!', 'section' => 2]); // Section 3 - page2 (manual completion). $page2 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'name' => 'Page2!', 'section' => 3, 'completion' => COMPLETION_TRACKING_MANUAL]); // Section 4 is empty. // Get basic details. get_fast_modinfo(0, 0, true); $modinfo = get_fast_modinfo($course); $sections['section1'] = $modinfo->get_section_info(1); $sections['section2'] = $modinfo->get_section_info(2); $sections['section3'] = $modinfo->get_section_info(3); $sections['section4'] = $modinfo->get_section_info(4); $page1cm = $modinfo->get_cm($page1->cmid); $prevvalue = condition::OPTION_PREVIOUS; if ($mark) { // Mark page1 complete. $completion = new \completion_info($course); $completion->update_state($page1cm, COMPLETION_COMPLETE); } $info = new \core_availability\mock_info_section($user->id, $sections[$section]); $cond = new condition((object)[ 'cm' => (int)$prevvalue, 'e' => $condition ]); $this->assertEquals($result, $cond->is_available(false, $info, true, $user->id)); $this->assertEquals($resultnot, $cond->is_available(true, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertMatchesRegularExpression($description, $information); } public function section_previous_activity_data(): array { return [ // Condition, Activity completion, section to test, result, resultnot, description. 'Completion complete Section with no previous activity' => [ COMPLETION_COMPLETE, false, 'section1', false, false, '~Missing activity.*is marked complete~' ], 'Completion incomplete Section with no previous activity' => [ COMPLETION_INCOMPLETE, false, 'section1', false, false, '~Missing activity.*is incomplete~' ], // Section 2 depending on section 1 -> Page 1 (no grading). 'Completion complete Section with previous activity incompleted' => [ COMPLETION_COMPLETE, false, 'section2', false, true, '~Page1!.*is marked complete~' ], 'Completion incomplete Section with previous activity incompleted' => [ COMPLETION_INCOMPLETE, false, 'section2', true, false, '~Page1!.*is incomplete~' ], 'Completion complete Section with previous activity completed' => [ COMPLETION_COMPLETE, true, 'section2', true, false, '~Page1!.*is marked complete~' ], 'Completion incomplete Section with previous activity completed' => [ COMPLETION_INCOMPLETE, true, 'section2', false, true, '~Page1!.*is incomplete~' ], // Section 3 depending on section 1 -> Page 1 (no grading). 'Completion complete Section ignoring empty sections and activity incompleted' => [ COMPLETION_COMPLETE, false, 'section3', false, true, '~Page1!.*is marked complete~' ], 'Completion incomplete Section ignoring empty sections and activity incompleted' => [ COMPLETION_INCOMPLETE, false, 'section3', true, false, '~Page1!.*is incomplete~' ], 'Completion complete Section ignoring empty sections and activity completed' => [ COMPLETION_COMPLETE, true, 'section3', true, false, '~Page1!.*is marked complete~' ], 'Completion incomplete Section ignoring empty sections and activity completed' => [ COMPLETION_INCOMPLETE, true, 'section3', false, true, '~Page1!.*is incomplete~' ], // Section 4 depending on section 3 -> Page 2 (no grading). 'Completion complete Last section with previous activity incompleted' => [ COMPLETION_COMPLETE, false, 'section4', false, true, '~Page2!.*is marked complete~' ], 'Completion incomplete Last section with previous activity incompleted' => [ COMPLETION_INCOMPLETE, false, 'section4', true, false, '~Page2!.*is incomplete~' ], 'Completion complete Last section with previous activity completed' => [ COMPLETION_COMPLETE, true, 'section4', false, true, '~Page2!.*is marked complete~' ], 'Completion incomplete Last section with previous activity completed' => [ COMPLETION_INCOMPLETE, true, 'section4', true, false, '~Page2!.*is incomplete~' ], ]; } /** * Tests completion_value_used static function. */ public function test_completion_value_used() { global $CFG, $DB; $this->resetAfterTest(); $prevvalue = condition::OPTION_PREVIOUS; // Create course with completion turned on and some sections. $CFG->enablecompletion = true; $CFG->enableavailability = true; $generator = $this->getDataGenerator(); $course = $generator->create_course( ['numsections' => 1, 'enablecompletion' => 1], ['createsections' => true]); // Create six pages with manual completion. $page1 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $page2 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $page3 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $page4 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $page5 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $page6 = $generator->get_plugin_generator('mod_page')->create_instance( ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); // Set up page3 to depend on page1, and section1 to depend on page2. $DB->set_field('course_modules', 'availability', '{"op":"|","show":true,"c":[' . '{"type":"completion","e":1,"cm":' . $page1->cmid . '}]}', ['id' => $page3->cmid]); $DB->set_field('course_sections', 'availability', '{"op":"|","show":true,"c":[' . '{"type":"completion","e":1,"cm":' . $page2->cmid . '}]}', ['course' => $course->id, 'section' => 1]); // Set up page5 and page6 to depend on previous activity. $DB->set_field('course_modules', 'availability', '{"op":"|","show":true,"c":[' . '{"type":"completion","e":1,"cm":' . $prevvalue . '}]}', ['id' => $page5->cmid]); $DB->set_field('course_modules', 'availability', '{"op":"|","show":true,"c":[' . '{"type":"completion","e":1,"cm":' . $prevvalue . '}]}', ['id' => $page6->cmid]); // Check 1: nothing depends on page3 and page6 but something does on the others. $this->assertTrue(condition::completion_value_used( $course, $page1->cmid)); $this->assertTrue(condition::completion_value_used( $course, $page2->cmid)); $this->assertFalse(condition::completion_value_used( $course, $page3->cmid)); $this->assertTrue(condition::completion_value_used( $course, $page4->cmid)); $this->assertTrue(condition::completion_value_used( $course, $page5->cmid)); $this->assertFalse(condition::completion_value_used( $course, $page6->cmid)); } /** * Updates the grade of a user in the given assign module instance. * * @param \stdClass $assignrow Assignment row from database * @param int $userid User id * @param float $grade Grade */ protected static function set_grade($assignrow, $userid, $grade) { $grades = []; $grades[$userid] = (object)[ 'rawgrade' => $grade, 'userid' => $userid ]; $assignrow->cmidnumber = null; assign_grade_item_update($assignrow, $grades); } /** * Tests the update_dependency_id() function. */ public function test_update_dependency_id() { $cond = new condition((object)[ 'cm' => 42, 'e' => COMPLETION_COMPLETE, 'selfid' => 43 ]); $this->assertFalse($cond->update_dependency_id('frogs', 42, 540)); $this->assertFalse($cond->update_dependency_id('course_modules', 12, 34)); $this->assertTrue($cond->update_dependency_id('course_modules', 42, 456)); $after = $cond->save(); $this->assertEquals(456, $after->cm); // Test selfid updating. $cond = new condition((object)[ 'cm' => 42, 'e' => COMPLETION_COMPLETE ]); $this->assertFalse($cond->update_dependency_id('frogs', 43, 540)); $this->assertFalse($cond->update_dependency_id('course_modules', 12, 34)); $after = $cond->save(); $this->assertEquals(42, $after->cm); // Test on previous activity. $cond = new condition((object)[ 'cm' => condition::OPTION_PREVIOUS, 'e' => COMPLETION_COMPLETE ]); $this->assertFalse($cond->update_dependency_id('frogs', 43, 80)); $this->assertFalse($cond->update_dependency_id('course_modules', 12, 34)); $after = $cond->save(); $this->assertEquals(condition::OPTION_PREVIOUS, $after->cm); } } version.php 0000644 00000001764 15152232433 0006752 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Version info. * * @package availability_completion * @copyright 2014 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2022112800; $plugin->requires = 2022111800; $plugin->component = 'availability_completion'; classes/privacy/provider.php 0000644 00000003042 15152232433 0012220 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Privacy Subsystem implementation for availability_completion. * * @package availability_completion * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace availability_completion\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for availability_completion implementing null_provider. * * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason() : string { return 'privacy:metadata'; } } classes/frontend.php 0000644 00000010035 15152232433 0010530 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Front-end class. * * @package availability_completion * @copyright 2014 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace availability_completion; defined('MOODLE_INTERNAL') || die(); /** * Front-end class. * * @package availability_completion * @copyright 2014 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class frontend extends \core_availability\frontend { /** * @var array Cached init parameters */ protected $cacheparams = []; /** * @var string IDs of course, cm, and section for cache (if any) */ protected $cachekey = ''; protected function get_javascript_strings() { return ['option_complete', 'option_fail', 'option_incomplete', 'option_pass', 'label_cm', 'label_completion']; } protected function get_javascript_init_params($course, \cm_info $cm = null, \section_info $section = null) { // Use cached result if available. The cache is just because we call it // twice (once from allow_add) so it's nice to avoid doing all the // print_string calls twice. $cachekey = $course->id . ',' . ($cm ? $cm->id : '') . ($section ? $section->id : ''); if ($cachekey !== $this->cachekey) { // Get list of activities on course which have completion values, // to fill the dropdown. $context = \context_course::instance($course->id); $cms = []; $modinfo = get_fast_modinfo($course); $previouscm = false; foreach ($modinfo->cms as $id => $othercm) { // Add each course-module if it has completion turned on and is not // the one currently being edited. if ($othercm->completion && (empty($cm) || $cm->id != $id) && !$othercm->deletioninprogress) { $cms[] = (object)['id' => $id, 'name' => format_string($othercm->name, true, ['context' => $context]), 'completiongradeitemnumber' => $othercm->completiongradeitemnumber]; } if (count($cms) && (empty($cm) || $cm->id == $id)) { $previouscm = true; } } if ($previouscm) { $previous = (object)['id' => \availability_completion\condition::OPTION_PREVIOUS, 'name' => get_string('option_previous', 'availability_completion'), 'completiongradeitemnumber' => \availability_completion\condition::OPTION_PREVIOUS]; array_unshift($cms, $previous); } $this->cachekey = $cachekey; $this->cacheinitparams = [$cms]; } return $this->cacheinitparams; } protected function allow_add($course, \cm_info $cm = null, \section_info $section = null) { global $CFG; // Check if completion is enabled for the course. require_once($CFG->libdir . '/completionlib.php'); $info = new \completion_info($course); if (!$info->is_enabled()) { return false; } // Check if there's at least one other module with completion info. $params = $this->get_javascript_init_params($course, $cm, $section); return ((array)$params[0]) != false; } } classes/condition.php 0000644 00000046344 15152232433 0010713 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Activity completion condition. * * @package availability_completion * @copyright 2014 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace availability_completion; use cache; use core_availability\info; use core_availability\info_module; use core_availability\info_section; use stdClass; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/completionlib.php'); /** * Activity completion condition. * * @package availability_completion * @copyright 2014 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class condition extends \core_availability\condition { /** @var int previous module cm value used to calculate relative completions */ public const OPTION_PREVIOUS = -1; /** @var int ID of module that this depends on */ protected $cmid; /** @var array IDs of the current module and section */ protected $selfids; /** @var int Expected completion type (one of the COMPLETE_xx constants) */ protected $expectedcompletion; /** @var array Array of previous cmids used to calculate relative completions */ protected $modfastprevious = []; /** @var array Array of cmids previous to each course section */ protected $sectionfastprevious = []; /** @var array Array of modules used in these conditions for course */ protected static $modsusedincondition = []; /** * Constructor. * * @param \stdClass $structure Data structure from JSON decode * @throws \coding_exception If invalid data structure. */ public function __construct($structure) { // Get cmid. if (isset($structure->cm) && is_number($structure->cm)) { $this->cmid = (int)$structure->cm; } else { throw new \coding_exception('Missing or invalid ->cm for completion condition'); } // Get expected completion. if (isset($structure->e) && in_array($structure->e, [COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL])) { $this->expectedcompletion = $structure->e; } else { throw new \coding_exception('Missing or invalid ->e for completion condition'); } } /** * Saves tree data back to a structure object. * * @return stdClass Structure object (ready to be made into JSON format) */ public function save(): stdClass { return (object) [ 'type' => 'completion', 'cm' => $this->cmid, 'e' => $this->expectedcompletion, ]; } /** * Returns a JSON object which corresponds to a condition of this type. * * Intended for unit testing, as normally the JSON values are constructed * by JavaScript code. * * @param int $cmid Course-module id of other activity * @param int $expectedcompletion Expected completion value (COMPLETION_xx) * @return stdClass Object representing condition */ public static function get_json(int $cmid, int $expectedcompletion): stdClass { return (object) [ 'type' => 'completion', 'cm' => (int)$cmid, 'e' => (int)$expectedcompletion, ]; } /** * Determines whether a particular item is currently available * according to this availability condition. * * @see \core_availability\tree_node\update_after_restore * * @param bool $not Set true if we are inverting the condition * @param info $info Item we're checking * @param bool $grabthelot Performance hint: if true, caches information * required for all course-modules, to make the front page and similar * pages work more quickly (works only for current user) * @param int $userid User ID to check availability for * @return bool True if available */ public function is_available($not, info $info, $grabthelot, $userid): bool { list($selfcmid, $selfsectionid) = $this->get_selfids($info); $cmid = $this->get_cmid($info->get_course(), $selfcmid, $selfsectionid); $modinfo = $info->get_modinfo(); $completion = new \completion_info($modinfo->get_course()); if (!array_key_exists($cmid, $modinfo->cms) || $modinfo->cms[$cmid]->deletioninprogress) { // If the cmid cannot be found, always return false regardless // of the condition or $not state. (Will be displayed in the // information message.) $allow = false; } else { // The completion system caches its own data so no caching needed here. $completiondata = $completion->get_data((object)['id' => $cmid], $grabthelot, $userid); $allow = true; if ($this->expectedcompletion == COMPLETION_COMPLETE) { // Complete also allows the pass, fail states. switch ($completiondata->completionstate) { case COMPLETION_COMPLETE: case COMPLETION_COMPLETE_FAIL: case COMPLETION_COMPLETE_PASS: break; default: $allow = false; } } else { // Other values require exact match. if ($completiondata->completionstate != $this->expectedcompletion) { $allow = false; } } if ($not) { $allow = !$allow; } } return $allow; } /** * Return current item IDs (cmid and sectionid). * * @param info $info * @return int[] with [0] => cmid/null, [1] => sectionid/null */ public function get_selfids(info $info): array { if (isset($this->selfids)) { return $this->selfids; } if ($info instanceof info_module) { $cminfo = $info->get_course_module(); if (!empty($cminfo->id)) { $this->selfids = [$cminfo->id, null]; return $this->selfids; } } if ($info instanceof info_section) { $section = $info->get_section(); if (!empty($section->id)) { $this->selfids = [null, $section->id]; return $this->selfids; } } return [null, null]; } /** * Get the cmid referenced in the access restriction. * * @param stdClass $course course object * @param int|null $selfcmid current course-module ID or null * @param int|null $selfsectionid current course-section ID or null * @return int|null cmid or null if no referenced cm is found */ public function get_cmid(stdClass $course, ?int $selfcmid, ?int $selfsectionid): ?int { if ($this->cmid > 0) { return $this->cmid; } // If it's a relative completion, load fast browsing. if ($this->cmid == self::OPTION_PREVIOUS) { $prevcmid = $this->get_previous_cmid($course, $selfcmid, $selfsectionid); if ($prevcmid) { return $prevcmid; } } return null; } /** * Return the previous CM ID of an specific course-module or course-section. * * @param stdClass $course course object * @param int|null $selfcmid course-module ID or null * @param int|null $selfsectionid course-section ID or null * @return int|null */ private function get_previous_cmid(stdClass $course, ?int $selfcmid, ?int $selfsectionid): ?int { $this->load_course_structure($course); if (isset($this->modfastprevious[$selfcmid])) { return $this->modfastprevious[$selfcmid]; } if (isset($this->sectionfastprevious[$selfsectionid])) { return $this->sectionfastprevious[$selfsectionid]; } return null; } /** * Loads static information about a course elements previous activities. * * Populates two variables: * - $this->sectionprevious[] course-module previous to a cmid * - $this->sectionfastprevious[] course-section previous to a cmid * * @param stdClass $course course object */ private function load_course_structure(stdClass $course): void { // If already loaded we don't need to do anything. if (empty($this->modfastprevious)) { $previouscache = cache::make('availability_completion', 'previous_cache'); $this->modfastprevious = $previouscache->get("mod_{$course->id}"); $this->sectionfastprevious = $previouscache->get("sec_{$course->id}"); } if (!empty($this->modfastprevious)) { return; } if (empty($this->modfastprevious)) { $this->modfastprevious = []; $sectionprevious = []; $modinfo = get_fast_modinfo($course); $lastcmid = 0; foreach ($modinfo->cms as $othercm) { if ($othercm->deletioninprogress) { continue; } // Save first cm of every section. if (!isset($sectionprevious[$othercm->section])) { $sectionprevious[$othercm->section] = $lastcmid; } if ($lastcmid) { $this->modfastprevious[$othercm->id] = $lastcmid; } // Load previous to all cms with completion. if ($othercm->completion == COMPLETION_TRACKING_NONE) { continue; } $lastcmid = $othercm->id; } // Fill empty sections index. $isections = array_reverse($modinfo->get_section_info_all()); foreach ($isections as $section) { if (isset($sectionprevious[$section->id])) { $lastcmid = $sectionprevious[$section->id]; } else { $sectionprevious[$section->id] = $lastcmid; } } $this->sectionfastprevious = $sectionprevious; $previouscache->set("mod_{$course->id}", $this->modfastprevious); $previouscache->set("sec_{$course->id}", $this->sectionfastprevious); } } /** * Returns a more readable keyword corresponding to a completion state. * * Used to make lang strings easier to read. * * @param int $completionstate COMPLETION_xx constant * @return string Readable keyword */ protected static function get_lang_string_keyword(int $completionstate): string { switch($completionstate) { case COMPLETION_INCOMPLETE: return 'incomplete'; case COMPLETION_COMPLETE: return 'complete'; case COMPLETION_COMPLETE_PASS: return 'complete_pass'; case COMPLETION_COMPLETE_FAIL: return 'complete_fail'; default: throw new \coding_exception('Unexpected completion state: ' . $completionstate); } } /** * Obtains a string describing this restriction (whether or not * it actually applies). * * @param bool $full Set true if this is the 'full information' view * @param bool $not Set true if we are inverting the condition * @param info $info Item we're checking * @return string Information string (for admin) about all restrictions on * this item */ public function get_description($full, $not, info $info): string { global $USER; $str = 'requires_'; $course = $info->get_course(); list($selfcmid, $selfsectionid) = $this->get_selfids($info); $modname = ''; // On ajax duplicate get_fast_modinfo is called before $PAGE->set_context // so we cannot use $PAGE->user_is_editing(). $coursecontext = \context_course::instance($course->id); $editing = !empty($USER->editing) && has_capability('moodle/course:manageactivities', $coursecontext); if ($this->cmid == self::OPTION_PREVIOUS && $editing) { // Previous activity name could be inconsistent when editing due to partial page loadings. $str .= 'previous_'; } else { // Get name for module. $cmid = $this->get_cmid($course, $selfcmid, $selfsectionid); $modinfo = $info->get_modinfo(); if (!array_key_exists($cmid, $modinfo->cms) || $modinfo->cms[$cmid]->deletioninprogress) { $modname = get_string('missing', 'availability_completion'); } else { $modname = self::description_cm_name($modinfo->cms[$cmid]->id); } } // Work out which lang string to use depending on required completion status. if ($not) { // Convert NOT strings to use the equivalent where possible. switch ($this->expectedcompletion) { case COMPLETION_INCOMPLETE: $str .= self::get_lang_string_keyword(COMPLETION_COMPLETE); break; case COMPLETION_COMPLETE: $str .= self::get_lang_string_keyword(COMPLETION_INCOMPLETE); break; default: // The other two cases do not have direct opposites. $str .= 'not_' . self::get_lang_string_keyword($this->expectedcompletion); break; } } else { $str .= self::get_lang_string_keyword($this->expectedcompletion); } return get_string($str, 'availability_completion', $modname); } /** * Obtains a representation of the options of this condition as a string, * for debugging. * * @return string Text representation of parameters */ protected function get_debug_string(): string { switch ($this->expectedcompletion) { case COMPLETION_COMPLETE : $type = 'COMPLETE'; break; case COMPLETION_INCOMPLETE : $type = 'INCOMPLETE'; break; case COMPLETION_COMPLETE_PASS: $type = 'COMPLETE_PASS'; break; case COMPLETION_COMPLETE_FAIL: $type = 'COMPLETE_FAIL'; break; default: throw new \coding_exception('Unexpected expected completion'); } $cm = $this->cmid; if ($this->cmid == self::OPTION_PREVIOUS) { $cm = 'opprevious'; } return 'cm' . $cm . ' ' . $type; } /** * Updates this node after restore, returning true if anything changed. * * @see \core_availability\tree_node\update_after_restore * * @param string $restoreid Restore ID * @param int $courseid ID of target course * @param \base_logger $logger Logger for any warnings * @param string $name Name of this item (for use in warning messages) * @return bool True if there was any change */ public function update_after_restore($restoreid, $courseid, \base_logger $logger, $name): bool { global $DB; $res = false; // If we depend on the previous activity, no translation is needed. if ($this->cmid == self::OPTION_PREVIOUS) { return $res; } $rec = \restore_dbops::get_backup_ids_record($restoreid, 'course_module', $this->cmid); if (!$rec || !$rec->newitemid) { // If we are on the same course (e.g. duplicate) then we can just // use the existing one. if ($DB->record_exists('course_modules', ['id' => $this->cmid, 'course' => $courseid])) { return $res; } // Otherwise it's a warning. $this->cmid = 0; $logger->process('Restored item (' . $name . ') has availability condition on module that was not restored', \backup::LOG_WARNING); } else { $this->cmid = (int)$rec->newitemid; } return true; } /** * Used in course/lib.php because we need to disable the completion JS if * a completion value affects a conditional activity. * * @param \stdClass $course Moodle course object * @param int $cmid Course-module id * @return bool True if this is used in a condition, false otherwise */ public static function completion_value_used($course, $cmid): bool { // Have we already worked out a list of required completion values // for this course? If so just use that. if (!array_key_exists($course->id, self::$modsusedincondition)) { // We don't have data for this course, build it. $modinfo = get_fast_modinfo($course); self::$modsusedincondition[$course->id] = []; // Activities. foreach ($modinfo->cms as $othercm) { if (is_null($othercm->availability)) { continue; } $ci = new \core_availability\info_module($othercm); $tree = $ci->get_availability_tree(); foreach ($tree->get_all_children('availability_completion\condition') as $cond) { $condcmid = $cond->get_cmid($course, $othercm->id, null); if (!empty($condcmid)) { self::$modsusedincondition[$course->id][$condcmid] = true; } } } // Sections. foreach ($modinfo->get_section_info_all() as $section) { if (is_null($section->availability)) { continue; } $ci = new \core_availability\info_section($section); $tree = $ci->get_availability_tree(); foreach ($tree->get_all_children('availability_completion\condition') as $cond) { $condcmid = $cond->get_cmid($course, null, $section->id); if (!empty($condcmid)) { self::$modsusedincondition[$course->id][$condcmid] = true; } } } } return array_key_exists($cmid, self::$modsusedincondition[$course->id]); } /** * Wipes the static cache of modules used in a condition (for unit testing). */ public static function wipe_static_cache() { self::$modsusedincondition = []; } public function update_dependency_id($table, $oldid, $newid) { if ($table === 'course_modules' && (int)$this->cmid === (int)$oldid) { $this->cmid = $newid; return true; } else { return false; } } } yui/build/moodle-availability_completion-form/moodle-availability_completion-form-debug.js 0000644 00000007653 15152232433 0026350 0 ustar 00 YUI.add('moodle-availability_completion-form', function (Y, NAME) { /** * JavaScript for form editing completion conditions. * * @module moodle-availability_completion-form */ M.availability_completion = M.availability_completion || {}; /** * @class M.availability_completion.form * @extends M.core_availability.plugin */ M.availability_completion.form = Y.Object(M.core_availability.plugin); /** * Initialises this plugin. * * @method initInner * @param {Array} cms Array of objects containing cmid => name */ M.availability_completion.form.initInner = function(cms) { this.cms = cms; }; M.availability_completion.form.getNode = function(json) { // Create HTML structure. var html = '<span class="col-form-label pr-3"> ' + M.util.get_string('title', 'availability_completion') + '</span>' + ' <span class="availability-group form-group"><label>' + '<span class="accesshide">' + M.util.get_string('label_cm', 'availability_completion') + ' </span>' + '<select class="custom-select" name="cm" title="' + M.util.get_string('label_cm', 'availability_completion') + '">' + '<option value="0">' + M.util.get_string('choosedots', 'moodle') + '</option>'; for (var i = 0; i < this.cms.length; i++) { var cm = this.cms[i]; // String has already been escaped using format_string. html += '<option value="' + cm.id + '">' + cm.name + '</option>'; } html += '</select></label> <label><span class="accesshide">' + M.util.get_string('label_completion', 'availability_completion') + ' </span><select class="custom-select" ' + 'name="e" title="' + M.util.get_string('label_completion', 'availability_completion') + '">' + '<option value="1">' + M.util.get_string('option_complete', 'availability_completion') + '</option>' + '<option value="0">' + M.util.get_string('option_incomplete', 'availability_completion') + '</option>' + '<option value="2">' + M.util.get_string('option_pass', 'availability_completion') + '</option>' + '<option value="3">' + M.util.get_string('option_fail', 'availability_completion') + '</option>' + '</select></label></span>'; var node = Y.Node.create('<span class="form-inline">' + html + '</span>'); // Set initial values. if (json.cm !== undefined && node.one('select[name=cm] > option[value=' + json.cm + ']')) { node.one('select[name=cm]').set('value', '' + json.cm); } if (json.e !== undefined) { node.one('select[name=e]').set('value', '' + json.e); } // Add event handlers (first time only). if (!M.availability_completion.form.addedEvents) { M.availability_completion.form.addedEvents = true; var root = Y.one('.availability-field'); root.delegate('change', function() { // Whichever dropdown changed, just update the form. M.core_availability.form.update(); }, '.availability_completion select'); } return node; }; M.availability_completion.form.fillValue = function(value, node) { value.cm = parseInt(node.one('select[name=cm]').get('value'), 10); value.e = parseInt(node.one('select[name=e]').get('value'), 10); }; M.availability_completion.form.fillErrors = function(errors, node) { var cmid = parseInt(node.one('select[name=cm]').get('value'), 10); if (cmid === 0) { errors.push('availability_completion:error_selectcmid'); } var e = parseInt(node.one('select[name=e]').get('value'), 10); if (((e === 2) || (e === 3))) { this.cms.forEach(function(cm) { if (cm.id === cmid) { if (cm.completiongradeitemnumber === null) { errors.push('availability_completion:error_selectcmidpassfail'); } } }); } }; }, '@VERSION@', {"requires": ["base", "node", "event", "moodle-core_availability-form"]}); yui/build/moodle-availability_completion-form/moodle-availability_completion-form.js 0000644 00000007653 15152232433 0025264 0 ustar 00 YUI.add('moodle-availability_completion-form', function (Y, NAME) { /** * JavaScript for form editing completion conditions. * * @module moodle-availability_completion-form */ M.availability_completion = M.availability_completion || {}; /** * @class M.availability_completion.form * @extends M.core_availability.plugin */ M.availability_completion.form = Y.Object(M.core_availability.plugin); /** * Initialises this plugin. * * @method initInner * @param {Array} cms Array of objects containing cmid => name */ M.availability_completion.form.initInner = function(cms) { this.cms = cms; }; M.availability_completion.form.getNode = function(json) { // Create HTML structure. var html = '<span class="col-form-label pr-3"> ' + M.util.get_string('title', 'availability_completion') + '</span>' + ' <span class="availability-group form-group"><label>' + '<span class="accesshide">' + M.util.get_string('label_cm', 'availability_completion') + ' </span>' + '<select class="custom-select" name="cm" title="' + M.util.get_string('label_cm', 'availability_completion') + '">' + '<option value="0">' + M.util.get_string('choosedots', 'moodle') + '</option>'; for (var i = 0; i < this.cms.length; i++) { var cm = this.cms[i]; // String has already been escaped using format_string. html += '<option value="' + cm.id + '">' + cm.name + '</option>'; } html += '</select></label> <label><span class="accesshide">' + M.util.get_string('label_completion', 'availability_completion') + ' </span><select class="custom-select" ' + 'name="e" title="' + M.util.get_string('label_completion', 'availability_completion') + '">' + '<option value="1">' + M.util.get_string('option_complete', 'availability_completion') + '</option>' + '<option value="0">' + M.util.get_string('option_incomplete', 'availability_completion') + '</option>' + '<option value="2">' + M.util.get_string('option_pass', 'availability_completion') + '</option>' + '<option value="3">' + M.util.get_string('option_fail', 'availability_completion') + '</option>' + '</select></label></span>'; var node = Y.Node.create('<span class="form-inline">' + html + '</span>'); // Set initial values. if (json.cm !== undefined && node.one('select[name=cm] > option[value=' + json.cm + ']')) { node.one('select[name=cm]').set('value', '' + json.cm); } if (json.e !== undefined) { node.one('select[name=e]').set('value', '' + json.e); } // Add event handlers (first time only). if (!M.availability_completion.form.addedEvents) { M.availability_completion.form.addedEvents = true; var root = Y.one('.availability-field'); root.delegate('change', function() { // Whichever dropdown changed, just update the form. M.core_availability.form.update(); }, '.availability_completion select'); } return node; }; M.availability_completion.form.fillValue = function(value, node) { value.cm = parseInt(node.one('select[name=cm]').get('value'), 10); value.e = parseInt(node.one('select[name=e]').get('value'), 10); }; M.availability_completion.form.fillErrors = function(errors, node) { var cmid = parseInt(node.one('select[name=cm]').get('value'), 10); if (cmid === 0) { errors.push('availability_completion:error_selectcmid'); } var e = parseInt(node.one('select[name=e]').get('value'), 10); if (((e === 2) || (e === 3))) { this.cms.forEach(function(cm) { if (cm.id === cmid) { if (cm.completiongradeitemnumber === null) { errors.push('availability_completion:error_selectcmidpassfail'); } } }); } }; }, '@VERSION@', {"requires": ["base", "node", "event", "moodle-core_availability-form"]}); yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js 0000644 00000004765 15152232433 0026046 0 ustar 00 YUI.add("moodle-availability_completion-form",function(o,e){M.availability_completion=M.availability_completion||{},M.availability_completion.form=o.Object(M.core_availability.plugin),M.availability_completion.form.initInner=function(e){this.cms=e},M.availability_completion.form.getNode=function(e){for(var i,l,t='<span class="col-form-label pr-3"> '+M.util.get_string("title","availability_completion")+'</span> <span class="availability-group form-group"><label><span class="accesshide">'+M.util.get_string("label_cm","availability_completion")+' </span><select class="custom-select" name="cm" title="'+M.util.get_string("label_cm","availability_completion")+'"><option value="0">'+M.util.get_string("choosedots","moodle")+"</option>",a=0;a<this.cms.length;a++)t+='<option value="'+(i=this.cms[a]).id+'">'+i.name+"</option>";return t+='</select></label> <label><span class="accesshide">'+M.util.get_string("label_completion","availability_completion")+' </span><select class="custom-select" name="e" title="'+M.util.get_string("label_completion","availability_completion")+'"><option value="1">'+M.util.get_string("option_complete","availability_completion")+'</option><option value="0">'+M.util.get_string("option_incomplete","availability_completion")+'</option><option value="2">'+M.util.get_string("option_pass","availability_completion")+'</option><option value="3">'+M.util.get_string("option_fail","availability_completion")+"</option></select></label></span>",l=o.Node.create('<span class="form-inline">'+t+"</span>"),e.cm!==undefined&&l.one("select[name=cm] > option[value="+e.cm+"]")&&l.one("select[name=cm]").set("value",""+e.cm),e.e!==undefined&&l.one("select[name=e]").set("value",""+e.e),M.availability_completion.form.addedEvents||(M.availability_completion.form.addedEvents=!0,o.one(".availability-field").delegate("change",function(){M.core_availability.form.update()},".availability_completion select")),l},M.availability_completion.form.fillValue=function(e,i){e.cm=parseInt(i.one("select[name=cm]").get("value"),10),e.e=parseInt(i.one("select[name=e]").get("value"),10)},M.availability_completion.form.fillErrors=function(i,e){var l=parseInt(e.one("select[name=cm]").get("value"),10);0===l&&i.push("availability_completion:error_selectcmid"),2!==(e=parseInt(e.one("select[name=e]").get("value"),10))&&3!==e||this.cms.forEach(function(e){e.id===l&&null===e.completiongradeitemnumber&&i.push("availability_completion:error_selectcmidpassfail")})}},"@VERSION@",{requires:["base","node","event","moodle-core_availability-form"]}); yui/src/form/meta/form.json 0000644 00000000244 15152232433 0011670 0 ustar 00 { "moodle-availability_completion-form": { "requires": [ "base", "node", "event", "moodle-core_availability-form" ] } } yui/src/form/build.json 0000644 00000000247 15152232433 0011101 0 ustar 00 { "name": "moodle-availability_completion-form", "builds": { "moodle-availability_completion-form": { "jsfiles": [ "form.js" ] } } } yui/src/form/js/form.js 0000644 00000007411 15152232433 0011024 0 ustar 00 /** * JavaScript for form editing completion conditions. * * @module moodle-availability_completion-form */ M.availability_completion = M.availability_completion || {}; /** * @class M.availability_completion.form * @extends M.core_availability.plugin */ M.availability_completion.form = Y.Object(M.core_availability.plugin); /** * Initialises this plugin. * * @method initInner * @param {Array} cms Array of objects containing cmid => name */ M.availability_completion.form.initInner = function(cms) { this.cms = cms; }; M.availability_completion.form.getNode = function(json) { // Create HTML structure. var html = '<span class="col-form-label pr-3"> ' + M.util.get_string('title', 'availability_completion') + '</span>' + ' <span class="availability-group form-group"><label>' + '<span class="accesshide">' + M.util.get_string('label_cm', 'availability_completion') + ' </span>' + '<select class="custom-select" name="cm" title="' + M.util.get_string('label_cm', 'availability_completion') + '">' + '<option value="0">' + M.util.get_string('choosedots', 'moodle') + '</option>'; for (var i = 0; i < this.cms.length; i++) { var cm = this.cms[i]; // String has already been escaped using format_string. html += '<option value="' + cm.id + '">' + cm.name + '</option>'; } html += '</select></label> <label><span class="accesshide">' + M.util.get_string('label_completion', 'availability_completion') + ' </span><select class="custom-select" ' + 'name="e" title="' + M.util.get_string('label_completion', 'availability_completion') + '">' + '<option value="1">' + M.util.get_string('option_complete', 'availability_completion') + '</option>' + '<option value="0">' + M.util.get_string('option_incomplete', 'availability_completion') + '</option>' + '<option value="2">' + M.util.get_string('option_pass', 'availability_completion') + '</option>' + '<option value="3">' + M.util.get_string('option_fail', 'availability_completion') + '</option>' + '</select></label></span>'; var node = Y.Node.create('<span class="form-inline">' + html + '</span>'); // Set initial values. if (json.cm !== undefined && node.one('select[name=cm] > option[value=' + json.cm + ']')) { node.one('select[name=cm]').set('value', '' + json.cm); } if (json.e !== undefined) { node.one('select[name=e]').set('value', '' + json.e); } // Add event handlers (first time only). if (!M.availability_completion.form.addedEvents) { M.availability_completion.form.addedEvents = true; var root = Y.one('.availability-field'); root.delegate('change', function() { // Whichever dropdown changed, just update the form. M.core_availability.form.update(); }, '.availability_completion select'); } return node; }; M.availability_completion.form.fillValue = function(value, node) { value.cm = parseInt(node.one('select[name=cm]').get('value'), 10); value.e = parseInt(node.one('select[name=e]').get('value'), 10); }; M.availability_completion.form.fillErrors = function(errors, node) { var cmid = parseInt(node.one('select[name=cm]').get('value'), 10); if (cmid === 0) { errors.push('availability_completion:error_selectcmid'); } var e = parseInt(node.one('select[name=e]').get('value'), 10); if (((e === 2) || (e === 3))) { this.cms.forEach(function(cm) { if (cm.id === cmid) { if (cm.completiongradeitemnumber === null) { errors.push('availability_completion:error_selectcmidpassfail'); } } }); } }; lang/en/availability_completion.php 0000644 00000006260 15152232433 0013507 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Language strings. * * @package availability_completion * @copyright 2014 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['cachedef_previous_cache'] = 'Previous activity dependency information'; $string['description'] = 'Require students to complete (or not complete) another activity.'; $string['error_selectcmid'] = 'You must select an activity for the completion condition.'; $string['error_selectcmidpassfail'] = 'You must select an activity with "Require grade" completion condition set.'; $string['label_cm'] = 'Activity or resource'; $string['label_completion'] = 'Required completion status'; $string['missing'] = '(Missing activity)'; $string['option_complete'] = 'must be marked complete'; $string['option_fail'] = 'must be complete with fail grade'; $string['option_incomplete'] = 'must not be marked complete'; $string['option_pass'] = 'must be complete with pass grade'; $string['option_previous'] = 'Previous activity with completion'; $string['pluginname'] = 'Restriction by activity completion'; $string['requires_incomplete'] = 'The activity <strong>{$a}</strong> is incomplete'; $string['requires_complete'] = 'The activity <strong>{$a}</strong> is marked complete'; $string['requires_complete_pass'] = 'The activity <strong>{$a}</strong> is complete and passed'; $string['requires_complete_fail'] = 'The activity <strong>{$a}</strong> is complete and failed'; $string['requires_not_complete_pass'] = 'The activity <strong>{$a}</strong> is not complete and passed'; $string['requires_not_complete_fail'] = 'The activity <strong>{$a}</strong> is not complete and failed'; $string['requires_previous_incomplete'] = 'The <strong>previous activity with completion</strong> is incomplete'; $string['requires_previous_complete'] = 'The <strong>previous activity with completion</strong> is marked complete'; $string['requires_previous_complete_pass'] = 'The <strong>previous activity with completion</strong> is complete and passed'; $string['requires_previous_complete_fail'] = 'The <strong>previous activity with completion</strong> is complete and failed'; $string['requires_previous_not_complete_pass'] = 'The <strong>previous activity with completion</strong> is not complete and passed'; $string['requires_previous_not_complete_fail'] = 'The <strong>previous activity with completion</strong> is not complete and failed'; $string['title'] = 'Activity completion'; $string['privacy:metadata'] = 'The Restriction by activity completion plugin does not store any personal data.';
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | ���֧ߧ֧�ѧ�ڧ� ����ѧߧڧ��: 0.01 |
proxy
|
phpinfo
|
���ѧ����ۧܧ�