From c54b183c8d74bfa9229b79e1d1f1dd0fa634c0d4 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Mon, 23 Dec 2019 16:38:50 +0100 Subject: [PATCH 1/2] MDL-67613 availability_completion: add previous activity condition --- .../completion/classes/condition.php | 274 +++++++++++-- .../condition/completion/classes/frontend.php | 11 +- .../condition/completion/db/caches.php | 35 ++ .../lang/en/availability_completion.php | 8 + .../availability_completion_previous.feature | 200 +++++++++ .../completion/tests/condition_test.php | 379 +++++++++++++++++- availability/condition/completion/version.php | 2 +- .../tests/fixtures/mock_info_module.php | 115 ++++++ .../tests/fixtures/mock_info_section.php | 116 ++++++ 9 files changed, 1104 insertions(+), 36 deletions(-) create mode 100644 availability/condition/completion/db/caches.php create mode 100644 availability/condition/completion/tests/behat/availability_completion_previous.feature create mode 100644 availability/tests/fixtures/mock_info_module.php create mode 100644 availability/tests/fixtures/mock_info_section.php diff --git a/availability/condition/completion/classes/condition.php b/availability/condition/completion/classes/condition.php index 11f86c82ce9..b4310e84e33 100644 --- a/availability/condition/completion/classes/condition.php +++ b/availability/condition/completion/classes/condition.php @@ -24,6 +24,12 @@ 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'); @@ -36,12 +42,25 @@ require_once($CFG->libdir . '/completionlib.php'); * @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 = array(); + + /** @var array Array of cmids previous to each course section */ + protected $sectionfastprevious = array(); + /** @var array Array of modules used in these conditions for course */ protected static $modsusedincondition = array(); @@ -58,7 +77,6 @@ class condition extends \core_availability\condition { } else { throw new \coding_exception('Missing or invalid ->cm for completion condition'); } - // Get expected completion. if (isset($structure->e) && in_array($structure->e, array(COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, @@ -69,7 +87,12 @@ class condition extends \core_availability\condition { } } - public function save() { + /** + * 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)array('type' => 'completion', 'cm' => $this->cmid, 'e' => $this->expectedcompletion); } @@ -84,22 +107,38 @@ class condition extends \core_availability\condition { * @param int $expectedcompletion Expected completion value (COMPLETION_xx) * @return stdClass Object representing condition */ - public static function get_json($cmid, $expectedcompletion) { + public static function get_json(int $cmid, int $expectedcompletion): stdClass { return (object)array('type' => 'completion', 'cm' => (int)$cmid, 'e' => (int)$expectedcompletion); } - public function is_available($not, \core_availability\info $info, $grabthelot, $userid) { + /** + * 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($this->cmid, $modinfo->cms)) { + 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)array('id' => $this->cmid), + $completiondata = $completion->get_data((object)array('id' => $cmid), $grabthelot, $userid, $modinfo); $allow = true; @@ -128,6 +167,134 @@ class condition extends \core_availability\condition { 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 = array($cminfo->id, null); + return $this->selfids; + } + } + if ($info instanceof info_section) { + $section = $info->get_section(); + if (!empty($section->id)) { + $this->selfids = array(null, $section->id); + return $this->selfids; + } + + } + return array(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 = array(); + $sectionprevious = array(); + + $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; + } + // Load previous to all cms with completion. + if ($othercm->completion == COMPLETION_TRACKING_NONE) { + continue; + } + if ($lastcmid) { + $this->modfastprevious[$othercm->id] = $lastcmid; + } + $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. * @@ -136,7 +303,7 @@ class condition extends \core_availability\condition { * @param int $completionstate COMPLETION_xx constant * @return string Readable keyword */ - protected static function get_lang_string_keyword($completionstate) { + protected static function get_lang_string_keyword(int $completionstate): string { switch($completionstate) { case COMPLETION_INCOMPLETE: return 'incomplete'; @@ -151,38 +318,69 @@ class condition extends \core_availability\condition { } } - public function get_description($full, $not, \core_availability\info $info) { - // Get name for module. - $modinfo = $info->get_modinfo(); - if (!array_key_exists($this->cmid, $modinfo->cms)) { - $modname = get_string('missing', 'availability_completion'); + /** + * 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 { - $modname = 'cms[$this->cmid]->id . '/>'; + // 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 = 'cms[$cmid]->id . '/>'; + } } - // Work out which lang string to use. + // 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 = 'requires_' . self::get_lang_string_keyword(COMPLETION_COMPLETE); + $str .= self::get_lang_string_keyword(COMPLETION_COMPLETE); break; case COMPLETION_COMPLETE: - $str = 'requires_' . self::get_lang_string_keyword(COMPLETION_INCOMPLETE); + $str .= self::get_lang_string_keyword(COMPLETION_INCOMPLETE); break; default: // The other two cases do not have direct opposites. - $str = 'requires_not_' . self::get_lang_string_keyword($this->expectedcompletion); + $str .= 'not_' . self::get_lang_string_keyword($this->expectedcompletion); break; } } else { - $str = 'requires_' . self::get_lang_string_keyword($this->expectedcompletion); + $str .= self::get_lang_string_keyword($this->expectedcompletion); } return get_string($str, 'availability_completion', $modname); } - protected function get_debug_string() { + /** + * 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'; @@ -199,18 +397,38 @@ class condition extends \core_availability\condition { default: throw new \coding_exception('Unexpected expected completion'); } - return 'cm' . $this->cmid . ' ' . $type; + $cm = $this->cmid; + if ($this->cmid == self::OPTION_PREVIOUS) { + $cm = 'opprevious'; + } + return 'cm' . $cm . ' ' . $type; } - public function update_after_restore($restoreid, $courseid, \base_logger $logger, $name) { + /** + * 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', array('id' => $this->cmid, 'course' => $courseid))) { - return false; + return $res; } // Otherwise it's a warning. $this->cmid = 0; @@ -231,7 +449,7 @@ class condition extends \core_availability\condition { * @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) { + 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)) { @@ -247,7 +465,10 @@ class condition extends \core_availability\condition { $ci = new \core_availability\info_module($othercm); $tree = $ci->get_availability_tree(); foreach ($tree->get_all_children('availability_completion\condition') as $cond) { - self::$modsusedincondition[$course->id][$cond->cmid] = true; + $condcmid = $cond->get_cmid($course, $othercm->id, null); + if (!empty($condcmid)) { + self::$modsusedincondition[$course->id][$condcmid] = true; + } } } @@ -259,7 +480,10 @@ class condition extends \core_availability\condition { $ci = new \core_availability\info_section($section); $tree = $ci->get_availability_tree(); foreach ($tree->get_all_children('availability_completion\condition') as $cond) { - self::$modsusedincondition[$course->id][$cond->cmid] = true; + $condcmid = $cond->get_cmid($course, null, $section->id); + if (!empty($condcmid)) { + self::$modsusedincondition[$course->id][$condcmid] = true; + } } } } diff --git a/availability/condition/completion/classes/frontend.php b/availability/condition/completion/classes/frontend.php index 7427328c4dc..46597171546 100644 --- a/availability/condition/completion/classes/frontend.php +++ b/availability/condition/completion/classes/frontend.php @@ -61,6 +61,7 @@ class frontend extends \core_availability\frontend { $context = \context_course::instance($course->id); $cms = array(); $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. @@ -69,8 +70,16 @@ class frontend extends \core_availability\frontend { 'name' => format_string($othercm->name, true, array('context' => $context)), 'completiongradeitemnumber' => $othercm->completiongradeitemnumber); } + if (count($cms) && (empty($cm) || $cm->id == $id)) { + $previouscm = true; + } + } + if ($previouscm) { + $previous = (object)array('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 = array($cms); } diff --git a/availability/condition/completion/db/caches.php b/availability/condition/completion/db/caches.php new file mode 100644 index 00000000000..e365533657d --- /dev/null +++ b/availability/condition/completion/db/caches.php @@ -0,0 +1,35 @@ +. + +/** + * Defined caches used internally by the plugin. + * + * @package availability_completion + * @category cache + * @copyright 2020 Ferran Recio + * @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 + ], +]; diff --git a/availability/condition/completion/lang/en/availability_completion.php b/availability/condition/completion/lang/en/availability_completion.php index f3a8693cf3a..4d406daca35 100644 --- a/availability/condition/completion/lang/en/availability_completion.php +++ b/availability/condition/completion/lang/en/availability_completion.php @@ -22,6 +22,7 @@ * @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.'; @@ -32,6 +33,7 @@ $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 {$a} is incomplete'; $string['requires_complete'] = 'The activity {$a} is marked complete'; @@ -39,5 +41,11 @@ $string['requires_complete_pass'] = 'The activity {$a} is compl $string['requires_complete_fail'] = 'The activity {$a} is complete and failed'; $string['requires_not_complete_pass'] = 'The activity {$a} is not complete and passed'; $string['requires_not_complete_fail'] = 'The activity {$a} is not complete and failed'; +$string['requires_previous_incomplete'] = 'The previous activity with completion is incomplete'; +$string['requires_previous_complete'] = 'The previous activity with completion is marked complete'; +$string['requires_previous_complete_pass'] = 'The previous activity with completion is complete and passed'; +$string['requires_previous_complete_fail'] = 'The previous activity with completion is complete and failed'; +$string['requires_previous_not_complete_pass'] = 'The previous activity with completion is not complete and passed'; +$string['requires_previous_not_complete_fail'] = 'The previous activity with completion is not complete and failed'; $string['title'] = 'Activity completion'; $string['privacy:metadata'] = 'The Restriction by activity completion plugin does not store any personal data.'; diff --git a/availability/condition/completion/tests/behat/availability_completion_previous.feature b/availability/condition/completion/tests/behat/availability_completion_previous.feature new file mode 100644 index 00000000000..ae21266a7c3 --- /dev/null +++ b/availability/condition/completion/tests/behat/availability_completion_previous.feature @@ -0,0 +1,200 @@ +@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 greyed-out if user does not 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 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 greyed-out if user does not 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 greyed-out if user does not 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 + 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 greyed-out if user does not 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 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 greyed-out if user does not 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 greyed-out if user does not 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 + + # 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 greyed-out if user does not 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 greyed-out if user does not 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" diff --git a/availability/condition/completion/tests/condition_test.php b/availability/condition/completion/tests/condition_test.php index c64be08752c..bb60039d917 100644 --- a/availability/condition/completion/tests/condition_test.php +++ b/availability/condition/completion/tests/condition_test.php @@ -37,13 +37,23 @@ require_once($CFG->libdir . '/completionlib.php'); * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class availability_completion_condition_testcase 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() { - // Load the mock info class so that it can be used. - global $CFG; - require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info.php'); + availability_completion\condition::wipe_static_cache(); } /** @@ -62,6 +72,8 @@ class availability_completion_condition_testcase extends advanced_testcase { $course = $generator->create_course(array('enablecompletion' => 1)); $page = $generator->get_plugin_generator('mod_page')->create_instance( array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); + $selfpage = $generator->get_plugin_generator('mod_page')->create_instance( + array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); $modinfo = get_fast_modinfo($course); $cm = $modinfo->get_cm($page->cmid); @@ -142,6 +154,12 @@ class availability_completion_condition_testcase extends advanced_testcase { $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); + } /** @@ -339,12 +357,318 @@ class availability_completion_condition_testcase extends advanced_testcase { $this->assertFalse($cond->is_available(false, $info, true, $user->id)); } + /** + * Tests the is_available and get_description functions for previous activity option. + * + * @dataProvider test_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(array('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( + array('course' => $course->id, 'name' => 'Page1!', + 'completion' => COMPLETION_TRACKING_MANUAL)); + + // Page 2 (manual completion). + $page2 = $generator->get_plugin_generator('mod_page')->create_instance( + array('course' => $course->id, 'name' => 'Page2!', + 'completion' => COMPLETION_TRACKING_MANUAL)); + + // Page ignored (no completion). + $pagenocompletion = $generator->get_plugin_generator('mod_page')->create_instance( + array('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', array( + 'course' => $course->id, 'name' => 'Assign!', + 'completion' => COMPLETION_TRACKING_AUTOMATIC)); + $DB->set_field('course_modules', 'completiongradeitemnumber', 0, + array('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( + array('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, array('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)array( + '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->assertRegExp($description, $information); + } + + public function test_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 test_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( + array('numsections' => 4, 'enablecompletion' => 1), + array('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( + array('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( + array('course' => $course, 'name' => 'Page ignored!', 'section' => 1)); + + // Section 2 - page ignored 2 (no completion). + $pagenocompletion2 = $generator->get_plugin_generator('mod_page')->create_instance( + array('course' => $course, 'name' => 'Page ignored!', 'section' => 2)); + + // Section 3 - page2 (manual completion). + $page2 = $generator->get_plugin_generator('mod_page')->create_instance( + array('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)array( + '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->assertRegExp($description, $information); + + } + + public function test_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; @@ -353,15 +677,20 @@ class availability_completion_condition_testcase extends advanced_testcase { $course = $generator->create_course( array('numsections' => 1, 'enablecompletion' => 1), array('createsections' => true)); - availability_completion\condition::wipe_static_cache(); - // Create three pages with manual completion. + // Create six pages with manual completion. $page1 = $generator->get_plugin_generator('mod_page')->create_instance( array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); $page2 = $generator->get_plugin_generator('mod_page')->create_instance( array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); $page3 = $generator->get_plugin_generator('mod_page')->create_instance( array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); + $page4 = $generator->get_plugin_generator('mod_page')->create_instance( + array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); + $page5 = $generator->get_plugin_generator('mod_page')->create_instance( + array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); + $page6 = $generator->get_plugin_generator('mod_page')->create_instance( + array('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', @@ -372,14 +701,29 @@ class availability_completion_condition_testcase extends advanced_testcase { '{"op":"|","show":true,"c":[' . '{"type":"completion","e":1,"cm":' . $page2->cmid . '}]}', array('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 . '}]}', + array('id' => $page5->cmid)); + $DB->set_field('course_modules', 'availability', + '{"op":"|","show":true,"c":[' . + '{"type":"completion","e":1,"cm":' . $prevvalue . '}]}', + array('id' => $page6->cmid)); - // Now check: nothing depends on page3 but something does on the others. + // Check 1: nothing depends on page3 and page6 but something does on the others. $this->assertTrue(availability_completion\condition::completion_value_used( $course, $page1->cmid)); $this->assertTrue(availability_completion\condition::completion_value_used( $course, $page2->cmid)); $this->assertFalse(availability_completion\condition::completion_value_used( $course, $page3->cmid)); + $this->assertTrue(availability_completion\condition::completion_value_used( + $course, $page4->cmid)); + $this->assertTrue(availability_completion\condition::completion_value_used( + $course, $page5->cmid)); + $this->assertFalse(availability_completion\condition::completion_value_used( + $course, $page6->cmid)); } /** @@ -402,11 +746,28 @@ class availability_completion_condition_testcase extends advanced_testcase { */ public function test_update_dependency_id() { $cond = new condition((object)array( - 'cm' => 123, 'e' => COMPLETION_COMPLETE)); - $this->assertFalse($cond->update_dependency_id('frogs', 123, 456)); + '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', 123, 456)); + $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)array( + '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)array( + '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); } } diff --git a/availability/condition/completion/version.php b/availability/condition/completion/version.php index 71d62819749..4e1a55f8f1c 100644 --- a/availability/condition/completion/version.php +++ b/availability/condition/completion/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019111800; +$plugin->version = 2020032600; $plugin->requires = 2019111200; $plugin->component = 'availability_completion'; diff --git a/availability/tests/fixtures/mock_info_module.php b/availability/tests/fixtures/mock_info_module.php new file mode 100644 index 00000000000..47f75cc3fa7 --- /dev/null +++ b/availability/tests/fixtures/mock_info_module.php @@ -0,0 +1,115 @@ +. + +/** + * For use in unit tests that require an info module which isn't really used. + * + * @package core_availability + * @copyright 2019 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_availability; + +defined('MOODLE_INTERNAL') || die(); + +/** + * For use in unit tests that require an info module which isn't really used. + * + * @package core_availability + * @copyright 2019 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mock_info_module extends info_module { + /** @var int User id for modinfo */ + protected $userid; + + /** @var \cm_info Activity. */ + protected $cm; + + /** + * Constructs with item details. + * + * @param int $userid Userid for modinfo (if used) + * @param \cm_info $cm Course-module object + */ + public function __construct($userid = 0, \cm_info $cm = null) { + parent::__construct($cm); + $this->userid = $userid; + $this->cm = $cm; + } + + /** + * Just returns a mock name. + * + * @return string Name of item + */ + protected function get_thing_name() { + return 'Mock Module'; + } + + /** + * Returns the current context. + * + * @return \context Context for this item + */ + public function get_context() { + return \context_course::instance($this->get_course()->id); + } + + /** + * Returns the cappability used to ignore access restrictions. + * + * @return string Name of capability used to view hidden items of this type + */ + protected function get_view_hidden_capability() { + return 'moodle/course:ignoreavailabilityrestrictions'; + } + + /** + * Mocks don't need to save anything into DB. + * + * @param string $availability New JSON value + */ + protected function set_in_database($availability) { + } + + /** + * Obtains the modinfo associated with this availability information. + * + * Note: This field is available ONLY for use by conditions when calculating + * availability or information. + * + * @return \course_modinfo Modinfo + * @throws \coding_exception If called at incorrect times + */ + public function get_modinfo() { + // Allow modinfo usage outside is_available etc., so we can use this + // to directly call into condition is_available. + if (!$this->userid) { + throw new \coding_exception('Need to set mock_info userid'); + } + return get_fast_modinfo($this->course, $this->userid); + } + + /** + * Override course-module info. + * @param \cm_info $cm + */ + public function set_cm (\cm_info $cm) { + $this->cm = $cm; + } +} diff --git a/availability/tests/fixtures/mock_info_section.php b/availability/tests/fixtures/mock_info_section.php new file mode 100644 index 00000000000..15fba4597c2 --- /dev/null +++ b/availability/tests/fixtures/mock_info_section.php @@ -0,0 +1,116 @@ +. + +/** + * For use in unit tests that require an info section which isn't really used. + * + * @package core_availability + * @copyright 2019 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_availability; + +defined('MOODLE_INTERNAL') || die(); + +/** + * For use in unit tests that require an info section which isn't really used. + * + * @package core_availability + * @copyright 2019 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mock_info_section extends info_section { + /** @var int User id for modinfo */ + protected $userid; + + /** @var \section_info Section. */ + protected $section; + + /** + * Constructs with item details. + * + * @param int $userid Userid for modinfo (if used) + * @param \section_info $section Section object + */ + public function __construct($userid = 0, \section_info $section = null) { + parent::__construct($section); + $this->userid = $userid; + $this->section = $section; + } + + /** + * Just returns a mock name. + * + * @return string Name of item + */ + protected function get_thing_name() { + return 'Mock Section'; + } + + /** + * Returns the current context. + * + * @return \context Context for this item + */ + public function get_context() { + return \context_course::instance($this->get_course()->id); + } + + /** + * Returns the cappability used to ignore access restrictions. + * + * @return string Name of capability used to view hidden items of this type + */ + protected function get_view_hidden_capability() { + return 'moodle/course:ignoreavailabilityrestrictions'; + } + + /** + * Mocks don't need to save anything into DB. + * + * @param string $availability New JSON value + */ + protected function set_in_database($availability) { + } + + /** + * Obtains the modinfo associated with this availability information. + * + * Note: This field is available ONLY for use by conditions when calculating + * availability or information. + * + * @return \course_modinfo Modinfo + * @throws \coding_exception If called at incorrect times + */ + public function get_modinfo() { + // Allow modinfo usage outside is_available etc., so we can use this + // to directly call into condition is_available. + if (!$this->userid) { + throw new \coding_exception('Need to set mock_info userid'); + } + return get_fast_modinfo($this->course, $this->userid); + } + + /** + * Override section info. + * + * @param \section_info $section + */ + public function set_section (\section_info $section) { + $this->section = $section; + } +} From 1db6424ceb375a4da46f418ddd644a6cd2fb4738 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Fri, 17 Apr 2020 18:03:12 +0200 Subject: [PATCH 2/2] MDL-67613 availability_completion: replacing old arrays --- .../completion/classes/condition.php | 42 ++-- .../condition/completion/classes/frontend.php | 20 +- .../completion/tests/condition_test.php | 229 ++++++++++-------- 3 files changed, 165 insertions(+), 126 deletions(-) diff --git a/availability/condition/completion/classes/condition.php b/availability/condition/completion/classes/condition.php index b4310e84e33..991af644922 100644 --- a/availability/condition/completion/classes/condition.php +++ b/availability/condition/completion/classes/condition.php @@ -56,13 +56,13 @@ class condition extends \core_availability\condition { protected $expectedcompletion; /** @var array Array of previous cmids used to calculate relative completions */ - protected $modfastprevious = array(); + protected $modfastprevious = []; /** @var array Array of cmids previous to each course section */ - protected $sectionfastprevious = array(); + protected $sectionfastprevious = []; /** @var array Array of modules used in these conditions for course */ - protected static $modsusedincondition = array(); + protected static $modsusedincondition = []; /** * Constructor. @@ -79,8 +79,8 @@ class condition extends \core_availability\condition { } // Get expected completion. if (isset($structure->e) && in_array($structure->e, - array(COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, - COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL))) { + [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'); @@ -93,8 +93,11 @@ class condition extends \core_availability\condition { * @return stdClass Structure object (ready to be made into JSON format) */ public function save(): stdClass { - return (object)array('type' => 'completion', - 'cm' => $this->cmid, 'e' => $this->expectedcompletion); + return (object) [ + 'type' => 'completion', + 'cm' => $this->cmid, + 'e' => $this->expectedcompletion, + ]; } /** @@ -108,8 +111,11 @@ class condition extends \core_availability\condition { * @return stdClass Object representing condition */ public static function get_json(int $cmid, int $expectedcompletion): stdClass { - return (object)array('type' => 'completion', 'cm' => (int)$cmid, - 'e' => (int)$expectedcompletion); + return (object) [ + 'type' => 'completion', + 'cm' => (int)$cmid, + 'e' => (int)$expectedcompletion, + ]; } /** @@ -138,7 +144,7 @@ class condition extends \core_availability\condition { $allow = false; } else { // The completion system caches its own data so no caching needed here. - $completiondata = $completion->get_data((object)array('id' => $cmid), + $completiondata = $completion->get_data((object)['id' => $cmid], $grabthelot, $userid, $modinfo); $allow = true; @@ -180,19 +186,19 @@ class condition extends \core_availability\condition { if ($info instanceof info_module) { $cminfo = $info->get_course_module(); if (!empty($cminfo->id)) { - $this->selfids = array($cminfo->id, null); + $this->selfids = [$cminfo->id, null]; return $this->selfids; } } if ($info instanceof info_section) { $section = $info->get_section(); if (!empty($section->id)) { - $this->selfids = array(null, $section->id); + $this->selfids = [null, $section->id]; return $this->selfids; } } - return array(null, null); + return [null, null]; } /** @@ -258,8 +264,8 @@ class condition extends \core_availability\condition { } if (empty($this->modfastprevious)) { - $this->modfastprevious = array(); - $sectionprevious = array(); + $this->modfastprevious = []; + $sectionprevious = []; $modinfo = get_fast_modinfo($course); $lastcmid = 0; @@ -427,7 +433,7 @@ class condition extends \core_availability\condition { // If we are on the same course (e.g. duplicate) then we can just // use the existing one. if ($DB->record_exists('course_modules', - array('id' => $this->cmid, 'course' => $courseid))) { + ['id' => $this->cmid, 'course' => $courseid])) { return $res; } // Otherwise it's a warning. @@ -455,7 +461,7 @@ class condition extends \core_availability\condition { 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] = array(); + self::$modsusedincondition[$course->id] = []; // Activities. foreach ($modinfo->cms as $othercm) { @@ -494,7 +500,7 @@ class condition extends \core_availability\condition { * Wipes the static cache of modules used in a condition (for unit testing). */ public static function wipe_static_cache() { - self::$modsusedincondition = array(); + self::$modsusedincondition = []; } public function update_dependency_id($table, $oldid, $newid) { diff --git a/availability/condition/completion/classes/frontend.php b/availability/condition/completion/classes/frontend.php index 46597171546..c6466854874 100644 --- a/availability/condition/completion/classes/frontend.php +++ b/availability/condition/completion/classes/frontend.php @@ -37,7 +37,7 @@ class frontend extends \core_availability\frontend { /** * @var array Cached init parameters */ - protected $cacheparams = array(); + protected $cacheparams = []; /** * @var string IDs of course, cm, and section for cache (if any) @@ -45,8 +45,8 @@ class frontend extends \core_availability\frontend { protected $cachekey = ''; protected function get_javascript_strings() { - return array('option_complete', 'option_fail', 'option_incomplete', 'option_pass', - 'label_cm', 'label_completion'); + return ['option_complete', 'option_fail', 'option_incomplete', 'option_pass', + 'label_cm', 'label_completion']; } protected function get_javascript_init_params($course, \cm_info $cm = null, @@ -59,29 +59,29 @@ class frontend extends \core_availability\frontend { // Get list of activities on course which have completion values, // to fill the dropdown. $context = \context_course::instance($course->id); - $cms = array(); + $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)array('id' => $id, - 'name' => format_string($othercm->name, true, array('context' => $context)), - 'completiongradeitemnumber' => $othercm->completiongradeitemnumber); + $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)array('id' => \availability_completion\condition::OPTION_PREVIOUS, + $previous = (object)['id' => \availability_completion\condition::OPTION_PREVIOUS, 'name' => get_string('option_previous', 'availability_completion'), - 'completiongradeitemnumber' => \availability_completion\condition::OPTION_PREVIOUS); + 'completiongradeitemnumber' => \availability_completion\condition::OPTION_PREVIOUS]; array_unshift($cms, $previous); } $this->cachekey = $cachekey; - $this->cacheinitparams = array($cms); + $this->cacheinitparams = [$cms]; } return $this->cacheinitparams; } diff --git a/availability/condition/completion/tests/condition_test.php b/availability/condition/completion/tests/condition_test.php index bb60039d917..b1a004093c6 100644 --- a/availability/condition/completion/tests/condition_test.php +++ b/availability/condition/completion/tests/condition_test.php @@ -69,19 +69,27 @@ class availability_completion_condition_testcase extends advanced_testcase { $CFG->enablecompletion = true; $CFG->enableavailability = true; $generator = $this->getDataGenerator(); - $course = $generator->create_course(array('enablecompletion' => 1)); + $course = $generator->create_course(['enablecompletion' => 1]); $page = $generator->get_plugin_generator('mod_page')->create_instance( - array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); + ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $selfpage = $generator->get_plugin_generator('mod_page')->create_instance( - array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); + ['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)array('op' => '|', 'show' => true, 'c' => array( - (object)array('type' => 'completion', 'cm' => (int)$cm->id, - 'e' => COMPLETION_COMPLETE))); + $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). @@ -166,7 +174,7 @@ class availability_completion_condition_testcase extends advanced_testcase { * Tests the save() function. */ public function test_save() { - $structure = (object)array('cm' => 42, 'e' => COMPLETION_COMPLETE); + $structure = (object)['cm' => 42, 'e' => COMPLETION_COMPLETE]; $cond = new condition($structure); $structure->type = 'completion'; $this->assertEquals($structure, $cond->save()); @@ -184,24 +192,24 @@ class availability_completion_condition_testcase extends advanced_testcase { $CFG->enablecompletion = true; $CFG->enableavailability = true; $generator = $this->getDataGenerator(); - $course = $generator->create_course(array('enablecompletion' => 1)); + $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( - array('course' => $course->id, 'name' => 'Page!', - 'completion' => COMPLETION_TRACKING_MANUAL)); + ['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', array( - 'course' => $course->id, 'name' => 'Assign!', - 'completion' => COMPLETION_TRACKING_AUTOMATIC)); + $assignrow = $this->getDataGenerator()->create_module('assign', [ + 'course' => $course->id, 'name' => 'Assign!', + 'completion' => COMPLETION_TRACKING_AUTOMATIC]); $DB->set_field('course_modules', 'completiongradeitemnumber', 0, - array('id' => $assignrow->cmid)); + ['id' => $assignrow->cmid]); $assign = new assign(context_module::instance($assignrow->cmid), false, false); // Get basic details. @@ -211,8 +219,9 @@ class availability_completion_condition_testcase extends advanced_testcase { $info = new \core_availability\mock_info($course, $user->id); // COMPLETE state (false), positive and NOT. - $cond = new condition((object)array( - 'cm' => (int)$pagecm->id, 'e' => COMPLETION_COMPLETE)); + $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); @@ -220,8 +229,9 @@ class availability_completion_condition_testcase extends advanced_testcase { $this->assertTrue($cond->is_available(true, $info, true, $user->id)); // INCOMPLETE state (true). - $cond = new condition((object)array( - 'cm' => (int)$pagecm->id, 'e' => COMPLETION_INCOMPLETE)); + $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); @@ -233,8 +243,9 @@ class availability_completion_condition_testcase extends advanced_testcase { $completion->update_state($pagecm, COMPLETION_COMPLETE); // COMPLETE state (true). - $cond = new condition((object)array( - 'cm' => (int)$pagecm->id, 'e' => COMPLETION_COMPLETE)); + $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); @@ -242,8 +253,9 @@ class availability_completion_condition_testcase extends advanced_testcase { $this->assertRegExp('~Page!.*is incomplete~', $information); // INCOMPLETE state (false). - $cond = new condition((object)array( - 'cm' => (int)$pagecm->id, 'e' => COMPLETION_INCOMPLETE)); + $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); @@ -253,32 +265,36 @@ class availability_completion_condition_testcase extends advanced_testcase { // 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, array('gradepass' => 50.0)); + 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)array( - 'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE)); + $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)array( - 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE)); + $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)array( - 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS)); + $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->assertRegExp('~Assign!.*is complete and passed~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); - $cond = new condition((object)array( - 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL)); + $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); @@ -288,26 +304,30 @@ class availability_completion_condition_testcase extends advanced_testcase { // Change the grade to be complete and failed. self::set_grade($assignrow, $user->id, 40); - $cond = new condition((object)array( - 'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE)); + $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)array( - 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE)); + $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)array( - 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS)); + $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->assertRegExp('~Assign!.*is complete and passed~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); - $cond = new condition((object)array( - 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL)); + $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); @@ -317,26 +337,30 @@ class availability_completion_condition_testcase extends advanced_testcase { // Now change it to pass. self::set_grade($assignrow, $user->id, 60); - $cond = new condition((object)array( - 'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE)); + $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)array( - 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE)); + $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)array( - 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS)); + $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->assertRegExp('~Assign!.*is not complete and passed~', $information); - $cond = new condition((object)array( - 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL)); + $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); @@ -345,15 +369,17 @@ class availability_completion_condition_testcase extends advanced_testcase { // Simulate deletion of an activity by using an invalid cmid. These // conditions always fail, regardless of NOT flag or INCOMPLETE. - $cond = new condition((object)array( - 'cm' => ($assigncm->id + 100), 'e' => COMPLETION_COMPLETE)); + $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->assertRegExp('~(Missing activity).*is marked complete~', $information); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); - $cond = new condition((object)array( - 'cm' => ($assigncm->id + 100), 'e' => COMPLETION_INCOMPLETE)); + $cond = new condition((object)[ + 'cm' => ($assigncm->id + 100), 'e' => COMPLETION_INCOMPLETE + ]); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); } @@ -379,39 +405,40 @@ class availability_completion_condition_testcase extends advanced_testcase { $CFG->enablecompletion = true; $CFG->enableavailability = true; $generator = $this->getDataGenerator(); - $course = $generator->create_course(array('enablecompletion' => 1)); + $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( - array('course' => $course->id, 'name' => 'Page1!', - 'completion' => COMPLETION_TRACKING_MANUAL)); + ['course' => $course->id, 'name' => 'Page1!', + 'completion' => COMPLETION_TRACKING_MANUAL]); // Page 2 (manual completion). $page2 = $generator->get_plugin_generator('mod_page')->create_instance( - array('course' => $course->id, 'name' => 'Page2!', - 'completion' => COMPLETION_TRACKING_MANUAL)); + ['course' => $course->id, 'name' => 'Page2!', + 'completion' => COMPLETION_TRACKING_MANUAL]); // Page ignored (no completion). $pagenocompletion = $generator->get_plugin_generator('mod_page')->create_instance( - array('course' => $course->id, 'name' => 'Page ignored!')); + ['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', array( - 'course' => $course->id, 'name' => 'Assign!', - 'completion' => COMPLETION_TRACKING_AUTOMATIC)); + $assignrow = $this->getDataGenerator()->create_module('assign', [ + 'course' => $course->id, 'name' => 'Assign!', + 'completion' => COMPLETION_TRACKING_AUTOMATIC + ]); $DB->set_field('course_modules', 'completiongradeitemnumber', 0, - array('id' => $assignrow->cmid)); + ['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( - array('course' => $course->id, 'name' => 'Page3!', - 'completion' => COMPLETION_TRACKING_MANUAL)); + ['course' => $course->id, 'name' => 'Page3!', + 'completion' => COMPLETION_TRACKING_MANUAL]); // Get basic details. $activities = []; @@ -425,7 +452,7 @@ class availability_completion_condition_testcase extends advanced_testcase { // Setup gradings and completion. if ($grade) { $gradeitem = $assign->get_grade_item(); - grade_object::set_properties($gradeitem, array('gradepass' => 50.0)); + grade_object::set_properties($gradeitem, ['gradepass' => 50.0]); $gradeitem->update(); self::set_grade($assignrow, $user->id, $grade); } @@ -436,8 +463,9 @@ class availability_completion_condition_testcase extends advanced_testcase { // Set opprevious WITH non existent previous activity. $info = new \core_availability\mock_info_module($user->id, $activities[$activity]); - $cond = new condition((object)array( - 'cm' => (int)$prevvalue, 'e' => $condition)); + $cond = new condition((object)[ + 'cm' => (int)$prevvalue, 'e' => $condition + ]); // Do the checks. $this->assertEquals($result, $cond->is_available(false, $info, true, $user->id)); @@ -558,29 +586,29 @@ class availability_completion_condition_testcase extends advanced_testcase { $CFG->enableavailability = true; $generator = $this->getDataGenerator(); $course = $generator->create_course( - array('numsections' => 4, 'enablecompletion' => 1), - array('createsections' => true)); + ['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( - array('course' => $course->id, 'name' => 'Page1!', 'section' => 1, - 'completion' => COMPLETION_TRACKING_MANUAL)); + ['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( - array('course' => $course, 'name' => 'Page ignored!', 'section' => 1)); + ['course' => $course, 'name' => 'Page ignored!', 'section' => 1]); // Section 2 - page ignored 2 (no completion). $pagenocompletion2 = $generator->get_plugin_generator('mod_page')->create_instance( - array('course' => $course, 'name' => 'Page ignored!', 'section' => 2)); + ['course' => $course, 'name' => 'Page ignored!', 'section' => 2]); // Section 3 - page2 (manual completion). $page2 = $generator->get_plugin_generator('mod_page')->create_instance( - array('course' => $course->id, 'name' => 'Page2!', 'section' => 3, - 'completion' => COMPLETION_TRACKING_MANUAL)); + ['course' => $course->id, 'name' => 'Page2!', 'section' => 3, + 'completion' => COMPLETION_TRACKING_MANUAL]); // Section 4 is empty. @@ -601,8 +629,9 @@ class availability_completion_condition_testcase extends advanced_testcase { } $info = new \core_availability\mock_info_section($user->id, $sections[$section]); - $cond = new condition((object)array( - 'cm' => (int)$prevvalue, 'e' => $condition)); + $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); @@ -675,41 +704,41 @@ class availability_completion_condition_testcase extends advanced_testcase { $CFG->enableavailability = true; $generator = $this->getDataGenerator(); $course = $generator->create_course( - array('numsections' => 1, 'enablecompletion' => 1), - array('createsections' => true)); + ['numsections' => 1, 'enablecompletion' => 1], + ['createsections' => true]); // Create six pages with manual completion. $page1 = $generator->get_plugin_generator('mod_page')->create_instance( - array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); + ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $page2 = $generator->get_plugin_generator('mod_page')->create_instance( - array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); + ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $page3 = $generator->get_plugin_generator('mod_page')->create_instance( - array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); + ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $page4 = $generator->get_plugin_generator('mod_page')->create_instance( - array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); + ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $page5 = $generator->get_plugin_generator('mod_page')->create_instance( - array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); + ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); $page6 = $generator->get_plugin_generator('mod_page')->create_instance( - array('course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); + ['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 . '}]}', - array('id' => $page3->cmid)); + ['id' => $page3->cmid]); $DB->set_field('course_sections', 'availability', '{"op":"|","show":true,"c":[' . '{"type":"completion","e":1,"cm":' . $page2->cmid . '}]}', - array('course' => $course->id, 'section' => 1)); + ['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 . '}]}', - array('id' => $page5->cmid)); + ['id' => $page5->cmid]); $DB->set_field('course_modules', 'availability', '{"op":"|","show":true,"c":[' . '{"type":"completion","e":1,"cm":' . $prevvalue . '}]}', - array('id' => $page6->cmid)); + ['id' => $page6->cmid]); // Check 1: nothing depends on page3 and page6 but something does on the others. $this->assertTrue(availability_completion\condition::completion_value_used( @@ -734,9 +763,10 @@ class availability_completion_condition_testcase extends advanced_testcase { * @param float $grade Grade */ protected static function set_grade($assignrow, $userid, $grade) { - $grades = array(); - $grades[$userid] = (object)array( - 'rawgrade' => $grade, 'userid' => $userid); + $grades = []; + $grades[$userid] = (object)[ + 'rawgrade' => $grade, 'userid' => $userid + ]; $assignrow->cmidnumber = null; assign_grade_item_update($assignrow, $grades); } @@ -745,8 +775,9 @@ class availability_completion_condition_testcase extends advanced_testcase { * Tests the update_dependency_id() function. */ public function test_update_dependency_id() { - $cond = new condition((object)array( - 'cm' => 42, 'e' => COMPLETION_COMPLETE, 'selfid' => 43)); + $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)); @@ -754,17 +785,19 @@ class availability_completion_condition_testcase extends advanced_testcase { $this->assertEquals(456, $after->cm); // Test selfid updating. - $cond = new condition((object)array( - 'cm' => 42, 'e' => COMPLETION_COMPLETE)); + $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)array( - 'cm' => condition::OPTION_PREVIOUS, - 'e' => COMPLETION_COMPLETE)); + $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();