Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace availability_completion; 18 19 defined('MOODLE_INTERNAL') || die(); 20 21 global $CFG; 22 require_once($CFG->libdir . '/completionlib.php'); 23 24 /** 25 * Unit tests for the completion condition. 26 * 27 * @package availability_completion 28 * @copyright 2014 The Open University 29 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 */ 31 class condition_test extends \advanced_testcase { 32 33 /** 34 * Setup to ensure that fixtures are loaded. 35 */ 36 public static function setupBeforeClass(): void { 37 global $CFG; 38 // Load the mock info class so that it can be used. 39 require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info.php'); 40 require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info_module.php'); 41 require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info_section.php'); 42 } 43 44 /** 45 * Load required classes. 46 */ 47 public function setUp(): void { 48 condition::wipe_static_cache(); 49 } 50 51 /** 52 * Tests constructing and using condition as part of tree. 53 */ 54 public function test_in_tree() { 55 global $USER, $CFG; 56 $this->resetAfterTest(); 57 58 $this->setAdminUser(); 59 60 // Create course with completion turned on and a Page. 61 $CFG->enablecompletion = true; 62 $CFG->enableavailability = true; 63 $generator = $this->getDataGenerator(); 64 $course = $generator->create_course(['enablecompletion' => 1]); 65 $page = $generator->get_plugin_generator('mod_page')->create_instance( 66 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 67 $selfpage = $generator->get_plugin_generator('mod_page')->create_instance( 68 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 69 70 $modinfo = get_fast_modinfo($course); 71 $cm = $modinfo->get_cm($page->cmid); 72 $info = new \core_availability\mock_info($course, $USER->id); 73 74 $structure = (object)[ 75 'op' => '|', 76 'show' => true, 77 'c' => [ 78 (object)[ 79 'type' => 'completion', 80 'cm' => (int)$cm->id, 81 'e' => COMPLETION_COMPLETE 82 ] 83 ] 84 ]; 85 $tree = new \core_availability\tree($structure); 86 87 // Initial check (user has not completed activity). 88 $result = $tree->check_available(false, $info, true, $USER->id); 89 $this->assertFalse($result->is_available()); 90 91 // Mark activity complete. 92 $completion = new \completion_info($course); 93 $completion->update_state($cm, COMPLETION_COMPLETE); 94 95 // Now it's true! 96 $result = $tree->check_available(false, $info, true, $USER->id); 97 $this->assertTrue($result->is_available()); 98 } 99 100 /** 101 * Tests the constructor including error conditions. Also tests the 102 * string conversion feature (intended for debugging only). 103 */ 104 public function test_constructor() { 105 // No parameters. 106 $structure = new \stdClass(); 107 try { 108 $cond = new condition($structure); 109 $this->fail(); 110 } catch (\coding_exception $e) { 111 $this->assertStringContainsString('Missing or invalid ->cm', $e->getMessage()); 112 } 113 114 // Invalid $cm. 115 $structure->cm = 'hello'; 116 try { 117 $cond = new condition($structure); 118 $this->fail(); 119 } catch (\coding_exception $e) { 120 $this->assertStringContainsString('Missing or invalid ->cm', $e->getMessage()); 121 } 122 123 // Missing $e. 124 $structure->cm = 42; 125 try { 126 $cond = new condition($structure); 127 $this->fail(); 128 } catch (\coding_exception $e) { 129 $this->assertStringContainsString('Missing or invalid ->e', $e->getMessage()); 130 } 131 132 // Invalid $e. 133 $structure->e = 99; 134 try { 135 $cond = new condition($structure); 136 $this->fail(); 137 } catch (\coding_exception $e) { 138 $this->assertStringContainsString('Missing or invalid ->e', $e->getMessage()); 139 } 140 141 // Successful construct & display with all different expected values. 142 $structure->e = COMPLETION_COMPLETE; 143 $cond = new condition($structure); 144 $this->assertEquals('{completion:cm42 COMPLETE}', (string)$cond); 145 146 $structure->e = COMPLETION_COMPLETE_PASS; 147 $cond = new condition($structure); 148 $this->assertEquals('{completion:cm42 COMPLETE_PASS}', (string)$cond); 149 150 $structure->e = COMPLETION_COMPLETE_FAIL; 151 $cond = new condition($structure); 152 $this->assertEquals('{completion:cm42 COMPLETE_FAIL}', (string)$cond); 153 154 $structure->e = COMPLETION_INCOMPLETE; 155 $cond = new condition($structure); 156 $this->assertEquals('{completion:cm42 INCOMPLETE}', (string)$cond); 157 158 // Successful contruct with previous activity. 159 $structure->cm = condition::OPTION_PREVIOUS; 160 $cond = new condition($structure); 161 $this->assertEquals('{completion:cmopprevious INCOMPLETE}', (string)$cond); 162 163 } 164 165 /** 166 * Tests the save() function. 167 */ 168 public function test_save() { 169 $structure = (object)['cm' => 42, 'e' => COMPLETION_COMPLETE]; 170 $cond = new condition($structure); 171 $structure->type = 'completion'; 172 $this->assertEquals($structure, $cond->save()); 173 } 174 175 /** 176 * Tests the is_available and get_description functions. 177 */ 178 public function test_usage() { 179 global $CFG, $DB; 180 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 181 $this->resetAfterTest(); 182 183 // Create course with completion turned on. 184 $CFG->enablecompletion = true; 185 $CFG->enableavailability = true; 186 $generator = $this->getDataGenerator(); 187 $course = $generator->create_course(['enablecompletion' => 1]); 188 $user = $generator->create_user(); 189 $generator->enrol_user($user->id, $course->id); 190 $this->setUser($user); 191 192 // Create a Page with manual completion for basic checks. 193 $page = $generator->get_plugin_generator('mod_page')->create_instance( 194 ['course' => $course->id, 'name' => 'Page!', 195 'completion' => COMPLETION_TRACKING_MANUAL]); 196 197 // Create an assignment - we need to have something that can be graded 198 // so as to test the PASS/FAIL states. Set it up to be completed based 199 // on its grade item. 200 $assignrow = $this->getDataGenerator()->create_module('assign', [ 201 'course' => $course->id, 'name' => 'Assign!', 202 'completion' => COMPLETION_TRACKING_AUTOMATIC]); 203 $DB->set_field('course_modules', 'completiongradeitemnumber', 0, 204 ['id' => $assignrow->cmid]); 205 $assign = new \assign(\context_module::instance($assignrow->cmid), false, false); 206 207 // Get basic details. 208 $modinfo = get_fast_modinfo($course); 209 $pagecm = $modinfo->get_cm($page->cmid); 210 $assigncm = $assign->get_course_module(); 211 $info = new \core_availability\mock_info($course, $user->id); 212 213 // COMPLETE state (false), positive and NOT. 214 $cond = new condition((object)[ 215 'cm' => (int)$pagecm->id, 'e' => COMPLETION_COMPLETE 216 ]); 217 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 218 $information = $cond->get_description(false, false, $info); 219 $information = \core_availability\info::format_info($information, $course); 220 $this->assertMatchesRegularExpression('~Page!.*is marked complete~', $information); 221 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 222 223 // INCOMPLETE state (true). 224 $cond = new condition((object)[ 225 'cm' => (int)$pagecm->id, 'e' => COMPLETION_INCOMPLETE 226 ]); 227 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 228 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 229 $information = $cond->get_description(false, true, $info); 230 $information = \core_availability\info::format_info($information, $course); 231 $this->assertMatchesRegularExpression('~Page!.*is marked complete~', $information); 232 233 // Mark page complete. 234 $completion = new \completion_info($course); 235 $completion->update_state($pagecm, COMPLETION_COMPLETE); 236 237 // COMPLETE state (true). 238 $cond = new condition((object)[ 239 'cm' => (int)$pagecm->id, 'e' => COMPLETION_COMPLETE 240 ]); 241 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 242 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 243 $information = $cond->get_description(false, true, $info); 244 $information = \core_availability\info::format_info($information, $course); 245 $this->assertMatchesRegularExpression('~Page!.*is incomplete~', $information); 246 247 // INCOMPLETE state (false). 248 $cond = new condition((object)[ 249 'cm' => (int)$pagecm->id, 'e' => COMPLETION_INCOMPLETE 250 ]); 251 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 252 $information = $cond->get_description(false, false, $info); 253 $information = \core_availability\info::format_info($information, $course); 254 $this->assertMatchesRegularExpression('~Page!.*is incomplete~', $information); 255 $this->assertTrue($cond->is_available(true, $info, 256 true, $user->id)); 257 258 // We are going to need the grade item so that we can get pass/fails. 259 $gradeitem = $assign->get_grade_item(); 260 \grade_object::set_properties($gradeitem, ['gradepass' => 50.0]); 261 $gradeitem->update(); 262 263 // With no grade, it should return true for INCOMPLETE and false for 264 // the other three. 265 $cond = new condition((object)[ 266 'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE 267 ]); 268 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 269 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 270 271 $cond = new condition((object)[ 272 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE 273 ]); 274 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 275 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 276 277 // Check $information for COMPLETE_PASS and _FAIL as we haven't yet. 278 $cond = new condition((object)[ 279 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS 280 ]); 281 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 282 $information = $cond->get_description(false, false, $info); 283 $information = \core_availability\info::format_info($information, $course); 284 $this->assertMatchesRegularExpression('~Assign!.*is complete and passed~', $information); 285 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 286 287 $cond = new condition((object)[ 288 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL 289 ]); 290 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 291 $information = $cond->get_description(false, false, $info); 292 $information = \core_availability\info::format_info($information, $course); 293 $this->assertMatchesRegularExpression('~Assign!.*is complete and failed~', $information); 294 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 295 296 // Change the grade to be complete and failed. 297 self::set_grade($assignrow, $user->id, 40); 298 299 $cond = new condition((object)[ 300 'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE 301 ]); 302 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 303 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 304 305 $cond = new condition((object)[ 306 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE 307 ]); 308 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 309 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 310 311 $cond = new condition((object)[ 312 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS 313 ]); 314 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 315 $information = $cond->get_description(false, false, $info); 316 $information = \core_availability\info::format_info($information, $course); 317 $this->assertMatchesRegularExpression('~Assign!.*is complete and passed~', $information); 318 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 319 320 $cond = new condition((object)[ 321 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL 322 ]); 323 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 324 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 325 $information = $cond->get_description(false, true, $info); 326 $information = \core_availability\info::format_info($information, $course); 327 $this->assertMatchesRegularExpression('~Assign!.*is not complete and failed~', $information); 328 329 // Now change it to pass. 330 self::set_grade($assignrow, $user->id, 60); 331 332 $cond = new condition((object)[ 333 'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE 334 ]); 335 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 336 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 337 338 $cond = new condition((object)[ 339 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE 340 ]); 341 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 342 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 343 344 $cond = new condition((object)[ 345 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS 346 ]); 347 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 348 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 349 $information = $cond->get_description(false, true, $info); 350 $information = \core_availability\info::format_info($information, $course); 351 $this->assertMatchesRegularExpression('~Assign!.*is not complete and passed~', $information); 352 353 $cond = new condition((object)[ 354 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL 355 ]); 356 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 357 $information = $cond->get_description(false, false, $info); 358 $information = \core_availability\info::format_info($information, $course); 359 $this->assertMatchesRegularExpression('~Assign!.*is complete and failed~', $information); 360 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 361 362 // Simulate deletion of an activity by using an invalid cmid. These 363 // conditions always fail, regardless of NOT flag or INCOMPLETE. 364 $cond = new condition((object)[ 365 'cm' => ($assigncm->id + 100), 'e' => COMPLETION_COMPLETE 366 ]); 367 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 368 $information = $cond->get_description(false, false, $info); 369 $information = \core_availability\info::format_info($information, $course); 370 $this->assertMatchesRegularExpression('~(Missing activity).*is marked complete~', $information); 371 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 372 $cond = new condition((object)[ 373 'cm' => ($assigncm->id + 100), 'e' => COMPLETION_INCOMPLETE 374 ]); 375 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 376 } 377 378 /** 379 * Tests the is_available and get_description functions for previous activity option. 380 * 381 * @dataProvider previous_activity_data 382 * @param int $grade the current assign grade (0 for none) 383 * @param int $condition true for complete, false for incomplete 384 * @param string $mark activity to mark as complete 385 * @param string $activity activity name to test 386 * @param bool $result if it must be available or not 387 * @param bool $resultnot if it must be available when the condition is inverted 388 * @param string $description the availabiklity text to check 389 */ 390 public function test_previous_activity(int $grade, int $condition, string $mark, string $activity, 391 bool $result, bool $resultnot, string $description): void { 392 global $CFG, $DB; 393 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 394 $this->resetAfterTest(); 395 396 // Create course with completion turned on. 397 $CFG->enablecompletion = true; 398 $CFG->enableavailability = true; 399 $generator = $this->getDataGenerator(); 400 $course = $generator->create_course(['enablecompletion' => 1]); 401 $user = $generator->create_user(); 402 $generator->enrol_user($user->id, $course->id); 403 $this->setUser($user); 404 405 // Page 1 (manual completion). 406 $page1 = $generator->get_plugin_generator('mod_page')->create_instance( 407 ['course' => $course->id, 'name' => 'Page1!', 408 'completion' => COMPLETION_TRACKING_MANUAL]); 409 410 // Page 2 (manual completion). 411 $page2 = $generator->get_plugin_generator('mod_page')->create_instance( 412 ['course' => $course->id, 'name' => 'Page2!', 413 'completion' => COMPLETION_TRACKING_MANUAL]); 414 415 // Page ignored (no completion). 416 $pagenocompletion = $generator->get_plugin_generator('mod_page')->create_instance( 417 ['course' => $course->id, 'name' => 'Page ignored!']); 418 419 // Create an assignment - we need to have something that can be graded 420 // so as to test the PASS/FAIL states. Set it up to be completed based 421 // on its grade item. 422 $assignrow = $this->getDataGenerator()->create_module('assign', [ 423 'course' => $course->id, 'name' => 'Assign!', 424 'completion' => COMPLETION_TRACKING_AUTOMATIC 425 ]); 426 $DB->set_field('course_modules', 'completiongradeitemnumber', 0, 427 ['id' => $assignrow->cmid]); 428 $assign = new \assign(\context_module::instance($assignrow->cmid), false, false); 429 430 // Page 3 (manual completion). 431 $page3 = $generator->get_plugin_generator('mod_page')->create_instance( 432 ['course' => $course->id, 'name' => 'Page3!', 433 'completion' => COMPLETION_TRACKING_MANUAL]); 434 435 // Get basic details. 436 $activities = []; 437 $modinfo = get_fast_modinfo($course); 438 $activities['page1'] = $modinfo->get_cm($page1->cmid); 439 $activities['page2'] = $modinfo->get_cm($page2->cmid); 440 $activities['assign'] = $assign->get_course_module(); 441 $activities['page3'] = $modinfo->get_cm($page3->cmid); 442 $prevvalue = condition::OPTION_PREVIOUS; 443 444 // Setup gradings and completion. 445 if ($grade) { 446 $gradeitem = $assign->get_grade_item(); 447 \grade_object::set_properties($gradeitem, ['gradepass' => 50.0]); 448 $gradeitem->update(); 449 self::set_grade($assignrow, $user->id, $grade); 450 } 451 if ($mark) { 452 $completion = new \completion_info($course); 453 $completion->update_state($activities[$mark], COMPLETION_COMPLETE); 454 } 455 456 // Set opprevious WITH non existent previous activity. 457 $info = new \core_availability\mock_info_module($user->id, $activities[$activity]); 458 $cond = new condition((object)[ 459 'cm' => (int)$prevvalue, 'e' => $condition 460 ]); 461 462 // Do the checks. 463 $this->assertEquals($result, $cond->is_available(false, $info, true, $user->id)); 464 $this->assertEquals($resultnot, $cond->is_available(true, $info, true, $user->id)); 465 $information = $cond->get_description(false, false, $info); 466 $information = \core_availability\info::format_info($information, $course); 467 $this->assertMatchesRegularExpression($description, $information); 468 } 469 470 public function previous_activity_data(): array { 471 // Assign grade, condition, activity to complete, activity to test, result, resultnot, description. 472 return [ 473 'Missing previous activity complete' => [ 474 0, COMPLETION_COMPLETE, '', 'page1', false, false, '~Missing activity.*is marked complete~' 475 ], 476 'Missing previous activity incomplete' => [ 477 0, COMPLETION_INCOMPLETE, '', 'page1', false, false, '~Missing activity.*is incomplete~' 478 ], 479 'Previous complete condition with previous activity incompleted' => [ 480 0, COMPLETION_COMPLETE, '', 'page2', false, true, '~Page1!.*is marked complete~' 481 ], 482 'Previous incomplete condition with previous activity incompleted' => [ 483 0, COMPLETION_INCOMPLETE, '', 'page2', true, false, '~Page1!.*is incomplete~' 484 ], 485 'Previous complete condition with previous activity completed' => [ 486 0, COMPLETION_COMPLETE, 'page1', 'page2', true, false, '~Page1!.*is marked complete~' 487 ], 488 'Previous incomplete condition with previous activity completed' => [ 489 0, COMPLETION_INCOMPLETE, 'page1', 'page2', false, true, '~Page1!.*is incomplete~' 490 ], 491 // Depenging on page pass fail (pages are not gradable). 492 'Previous complete pass condition with previous no gradable activity incompleted' => [ 493 0, COMPLETION_COMPLETE_PASS, '', 'page2', false, true, '~Page1!.*is complete and passed~' 494 ], 495 'Previous complete fail condition with previous no gradable activity incompleted' => [ 496 0, COMPLETION_COMPLETE_FAIL, '', 'page2', false, true, '~Page1!.*is complete and failed~' 497 ], 498 'Previous complete pass condition with previous no gradable activity completed' => [ 499 0, COMPLETION_COMPLETE_PASS, 'page1', 'page2', false, true, '~Page1!.*is complete and passed~' 500 ], 501 'Previous complete fail condition with previous no gradable activity completed' => [ 502 0, COMPLETION_COMPLETE_FAIL, 'page1', 'page2', false, true, '~Page1!.*is complete and failed~' 503 ], 504 // There's an page without completion between page2 ans assign. 505 'Previous complete condition with sibling activity incompleted' => [ 506 0, COMPLETION_COMPLETE, '', 'assign', false, true, '~Page2!.*is marked complete~' 507 ], 508 'Previous incomplete condition with sibling activity incompleted' => [ 509 0, COMPLETION_INCOMPLETE, '', 'assign', true, false, '~Page2!.*is incomplete~' 510 ], 511 'Previous complete condition with sibling activity completed' => [ 512 0, COMPLETION_COMPLETE, 'page2', 'assign', true, false, '~Page2!.*is marked complete~' 513 ], 514 'Previous incomplete condition with sibling activity completed' => [ 515 0, COMPLETION_INCOMPLETE, 'page2', 'assign', false, true, '~Page2!.*is incomplete~' 516 ], 517 // Depending on assign without grade. 518 'Previous complete condition with previous without grade' => [ 519 0, COMPLETION_COMPLETE, '', 'page3', false, true, '~Assign!.*is marked complete~' 520 ], 521 'Previous incomplete condition with previous without grade' => [ 522 0, COMPLETION_INCOMPLETE, '', 'page3', true, false, '~Assign!.*is incomplete~' 523 ], 524 'Previous complete pass condition with previous without grade' => [ 525 0, COMPLETION_COMPLETE_PASS, '', 'page3', false, true, '~Assign!.*is complete and passed~' 526 ], 527 'Previous complete fail condition with previous without grade' => [ 528 0, COMPLETION_COMPLETE_FAIL, '', 'page3', false, true, '~Assign!.*is complete and failed~' 529 ], 530 // Depending on assign with grade. 531 'Previous complete condition with previous fail grade' => [ 532 40, COMPLETION_COMPLETE, '', 'page3', true, false, '~Assign!.*is marked complete~' 533 ], 534 'Previous incomplete condition with previous fail grade' => [ 535 40, COMPLETION_INCOMPLETE, '', 'page3', false, true, '~Assign!.*is incomplete~' 536 ], 537 'Previous complete pass condition with previous fail grade' => [ 538 40, COMPLETION_COMPLETE_PASS, '', 'page3', false, true, '~Assign!.*is complete and passed~' 539 ], 540 'Previous complete fail condition with previous fail grade' => [ 541 40, COMPLETION_COMPLETE_FAIL, '', 'page3', true, false, '~Assign!.*is complete and failed~' 542 ], 543 'Previous complete condition with previous pass grade' => [ 544 60, COMPLETION_COMPLETE, '', 'page3', true, false, '~Assign!.*is marked complete~' 545 ], 546 'Previous incomplete condition with previous pass grade' => [ 547 60, COMPLETION_INCOMPLETE, '', 'page3', false, true, '~Assign!.*is incomplete~' 548 ], 549 'Previous complete pass condition with previous pass grade' => [ 550 60, COMPLETION_COMPLETE_PASS, '', 'page3', true, false, '~Assign!.*is complete and passed~' 551 ], 552 'Previous complete fail condition with previous pass grade' => [ 553 60, COMPLETION_COMPLETE_FAIL, '', 'page3', false, true, '~Assign!.*is complete and failed~' 554 ], 555 ]; 556 } 557 558 /** 559 * Tests the is_available and get_description functions for 560 * previous activity option in course sections. 561 * 562 * @dataProvider section_previous_activity_data 563 * @param int $condition condition value 564 * @param bool $mark if Page 1 must be mark as completed 565 * @param string $section section to add the availability 566 * @param bool $result expected result 567 * @param bool $resultnot expected negated result 568 * @param string $description description to match 569 */ 570 public function test_section_previous_activity(int $condition, bool $mark, string $section, 571 bool $result, bool $resultnot, string $description): void { 572 global $CFG, $DB; 573 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 574 $this->resetAfterTest(); 575 576 // Create course with completion turned on. 577 $CFG->enablecompletion = true; 578 $CFG->enableavailability = true; 579 $generator = $this->getDataGenerator(); 580 $course = $generator->create_course( 581 ['numsections' => 4, 'enablecompletion' => 1], 582 ['createsections' => true]); 583 $user = $generator->create_user(); 584 $generator->enrol_user($user->id, $course->id); 585 $this->setUser($user); 586 587 // Section 1 - page1 (manual completion). 588 $page1 = $generator->get_plugin_generator('mod_page')->create_instance( 589 ['course' => $course->id, 'name' => 'Page1!', 'section' => 1, 590 'completion' => COMPLETION_TRACKING_MANUAL]); 591 592 // Section 1 - page ignored 1 (no completion). 593 $pagenocompletion1 = $generator->get_plugin_generator('mod_page')->create_instance( 594 ['course' => $course, 'name' => 'Page ignored!', 'section' => 1]); 595 596 // Section 2 - page ignored 2 (no completion). 597 $pagenocompletion2 = $generator->get_plugin_generator('mod_page')->create_instance( 598 ['course' => $course, 'name' => 'Page ignored!', 'section' => 2]); 599 600 // Section 3 - page2 (manual completion). 601 $page2 = $generator->get_plugin_generator('mod_page')->create_instance( 602 ['course' => $course->id, 'name' => 'Page2!', 'section' => 3, 603 'completion' => COMPLETION_TRACKING_MANUAL]); 604 605 // Section 4 is empty. 606 607 // Get basic details. 608 get_fast_modinfo(0, 0, true); 609 $modinfo = get_fast_modinfo($course); 610 $sections['section1'] = $modinfo->get_section_info(1); 611 $sections['section2'] = $modinfo->get_section_info(2); 612 $sections['section3'] = $modinfo->get_section_info(3); 613 $sections['section4'] = $modinfo->get_section_info(4); 614 $page1cm = $modinfo->get_cm($page1->cmid); 615 $prevvalue = condition::OPTION_PREVIOUS; 616 617 if ($mark) { 618 // Mark page1 complete. 619 $completion = new \completion_info($course); 620 $completion->update_state($page1cm, COMPLETION_COMPLETE); 621 } 622 623 $info = new \core_availability\mock_info_section($user->id, $sections[$section]); 624 $cond = new condition((object)[ 625 'cm' => (int)$prevvalue, 'e' => $condition 626 ]); 627 $this->assertEquals($result, $cond->is_available(false, $info, true, $user->id)); 628 $this->assertEquals($resultnot, $cond->is_available(true, $info, true, $user->id)); 629 $information = $cond->get_description(false, false, $info); 630 $information = \core_availability\info::format_info($information, $course); 631 $this->assertMatchesRegularExpression($description, $information); 632 633 } 634 635 public function section_previous_activity_data(): array { 636 return [ 637 // Condition, Activity completion, section to test, result, resultnot, description. 638 'Completion complete Section with no previous activity' => [ 639 COMPLETION_COMPLETE, false, 'section1', false, false, '~Missing activity.*is marked complete~' 640 ], 641 'Completion incomplete Section with no previous activity' => [ 642 COMPLETION_INCOMPLETE, false, 'section1', false, false, '~Missing activity.*is incomplete~' 643 ], 644 // Section 2 depending on section 1 -> Page 1 (no grading). 645 'Completion complete Section with previous activity incompleted' => [ 646 COMPLETION_COMPLETE, false, 'section2', false, true, '~Page1!.*is marked complete~' 647 ], 648 'Completion incomplete Section with previous activity incompleted' => [ 649 COMPLETION_INCOMPLETE, false, 'section2', true, false, '~Page1!.*is incomplete~' 650 ], 651 'Completion complete Section with previous activity completed' => [ 652 COMPLETION_COMPLETE, true, 'section2', true, false, '~Page1!.*is marked complete~' 653 ], 654 'Completion incomplete Section with previous activity completed' => [ 655 COMPLETION_INCOMPLETE, true, 'section2', false, true, '~Page1!.*is incomplete~' 656 ], 657 // Section 3 depending on section 1 -> Page 1 (no grading). 658 'Completion complete Section ignoring empty sections and activity incompleted' => [ 659 COMPLETION_COMPLETE, false, 'section3', false, true, '~Page1!.*is marked complete~' 660 ], 661 'Completion incomplete Section ignoring empty sections and activity incompleted' => [ 662 COMPLETION_INCOMPLETE, false, 'section3', true, false, '~Page1!.*is incomplete~' 663 ], 664 'Completion complete Section ignoring empty sections and activity completed' => [ 665 COMPLETION_COMPLETE, true, 'section3', true, false, '~Page1!.*is marked complete~' 666 ], 667 'Completion incomplete Section ignoring empty sections and activity completed' => [ 668 COMPLETION_INCOMPLETE, true, 'section3', false, true, '~Page1!.*is incomplete~' 669 ], 670 // Section 4 depending on section 3 -> Page 2 (no grading). 671 'Completion complete Last section with previous activity incompleted' => [ 672 COMPLETION_COMPLETE, false, 'section4', false, true, '~Page2!.*is marked complete~' 673 ], 674 'Completion incomplete Last section with previous activity incompleted' => [ 675 COMPLETION_INCOMPLETE, false, 'section4', true, false, '~Page2!.*is incomplete~' 676 ], 677 'Completion complete Last section with previous activity completed' => [ 678 COMPLETION_COMPLETE, true, 'section4', false, true, '~Page2!.*is marked complete~' 679 ], 680 'Completion incomplete Last section with previous activity completed' => [ 681 COMPLETION_INCOMPLETE, true, 'section4', true, false, '~Page2!.*is incomplete~' 682 ], 683 ]; 684 } 685 686 /** 687 * Tests completion_value_used static function. 688 */ 689 public function test_completion_value_used() { 690 global $CFG, $DB; 691 $this->resetAfterTest(); 692 $prevvalue = condition::OPTION_PREVIOUS; 693 694 // Create course with completion turned on and some sections. 695 $CFG->enablecompletion = true; 696 $CFG->enableavailability = true; 697 $generator = $this->getDataGenerator(); 698 $course = $generator->create_course( 699 ['numsections' => 1, 'enablecompletion' => 1], 700 ['createsections' => true]); 701 702 // Create six pages with manual completion. 703 $page1 = $generator->get_plugin_generator('mod_page')->create_instance( 704 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 705 $page2 = $generator->get_plugin_generator('mod_page')->create_instance( 706 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 707 $page3 = $generator->get_plugin_generator('mod_page')->create_instance( 708 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 709 $page4 = $generator->get_plugin_generator('mod_page')->create_instance( 710 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 711 $page5 = $generator->get_plugin_generator('mod_page')->create_instance( 712 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 713 $page6 = $generator->get_plugin_generator('mod_page')->create_instance( 714 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 715 716 // Set up page3 to depend on page1, and section1 to depend on page2. 717 $DB->set_field('course_modules', 'availability', 718 '{"op":"|","show":true,"c":[' . 719 '{"type":"completion","e":1,"cm":' . $page1->cmid . '}]}', 720 ['id' => $page3->cmid]); 721 $DB->set_field('course_sections', 'availability', 722 '{"op":"|","show":true,"c":[' . 723 '{"type":"completion","e":1,"cm":' . $page2->cmid . '}]}', 724 ['course' => $course->id, 'section' => 1]); 725 // Set up page5 and page6 to depend on previous activity. 726 $DB->set_field('course_modules', 'availability', 727 '{"op":"|","show":true,"c":[' . 728 '{"type":"completion","e":1,"cm":' . $prevvalue . '}]}', 729 ['id' => $page5->cmid]); 730 $DB->set_field('course_modules', 'availability', 731 '{"op":"|","show":true,"c":[' . 732 '{"type":"completion","e":1,"cm":' . $prevvalue . '}]}', 733 ['id' => $page6->cmid]); 734 735 // Check 1: nothing depends on page3 and page6 but something does on the others. 736 $this->assertTrue(condition::completion_value_used( 737 $course, $page1->cmid)); 738 $this->assertTrue(condition::completion_value_used( 739 $course, $page2->cmid)); 740 $this->assertFalse(condition::completion_value_used( 741 $course, $page3->cmid)); 742 $this->assertTrue(condition::completion_value_used( 743 $course, $page4->cmid)); 744 $this->assertTrue(condition::completion_value_used( 745 $course, $page5->cmid)); 746 $this->assertFalse(condition::completion_value_used( 747 $course, $page6->cmid)); 748 } 749 750 /** 751 * Updates the grade of a user in the given assign module instance. 752 * 753 * @param \stdClass $assignrow Assignment row from database 754 * @param int $userid User id 755 * @param float $grade Grade 756 */ 757 protected static function set_grade($assignrow, $userid, $grade) { 758 $grades = []; 759 $grades[$userid] = (object)[ 760 'rawgrade' => $grade, 'userid' => $userid 761 ]; 762 $assignrow->cmidnumber = null; 763 assign_grade_item_update($assignrow, $grades); 764 } 765 766 /** 767 * Tests the update_dependency_id() function. 768 */ 769 public function test_update_dependency_id() { 770 $cond = new condition((object)[ 771 'cm' => 42, 'e' => COMPLETION_COMPLETE, 'selfid' => 43 772 ]); 773 $this->assertFalse($cond->update_dependency_id('frogs', 42, 540)); 774 $this->assertFalse($cond->update_dependency_id('course_modules', 12, 34)); 775 $this->assertTrue($cond->update_dependency_id('course_modules', 42, 456)); 776 $after = $cond->save(); 777 $this->assertEquals(456, $after->cm); 778 779 // Test selfid updating. 780 $cond = new condition((object)[ 781 'cm' => 42, 'e' => COMPLETION_COMPLETE 782 ]); 783 $this->assertFalse($cond->update_dependency_id('frogs', 43, 540)); 784 $this->assertFalse($cond->update_dependency_id('course_modules', 12, 34)); 785 $after = $cond->save(); 786 $this->assertEquals(42, $after->cm); 787 788 // Test on previous activity. 789 $cond = new condition((object)[ 790 'cm' => condition::OPTION_PREVIOUS, 791 'e' => COMPLETION_COMPLETE 792 ]); 793 $this->assertFalse($cond->update_dependency_id('frogs', 43, 80)); 794 $this->assertFalse($cond->update_dependency_id('course_modules', 12, 34)); 795 $after = $cond->save(); 796 $this->assertEquals(condition::OPTION_PREVIOUS, $after->cm); 797 } 798 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body