Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403]
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 core_availability; 18 19 /** 20 * Unit tests for the condition tree class and related logic. 21 * 22 * @package core_availability 23 * @copyright 2014 The Open University 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 */ 26 class tree_test extends \advanced_testcase { 27 public function setUp(): void { 28 // Load the mock classes so they can be used. 29 require_once (__DIR__ . '/fixtures/mock_condition.php'); 30 require_once (__DIR__ . '/fixtures/mock_info.php'); 31 } 32 33 /** 34 * Tests constructing a tree with errors. 35 */ 36 public function test_construct_errors() { 37 try { 38 new tree('frog'); 39 $this->fail(); 40 } catch (\coding_exception $e) { 41 $this->assertStringContainsString('not object', $e->getMessage()); 42 } 43 try { 44 new tree((object)array()); 45 $this->fail(); 46 } catch (\coding_exception $e) { 47 $this->assertStringContainsString('missing ->op', $e->getMessage()); 48 } 49 try { 50 new tree((object)array('op' => '*')); 51 $this->fail(); 52 } catch (\coding_exception $e) { 53 $this->assertStringContainsString('unknown ->op', $e->getMessage()); 54 } 55 try { 56 new tree((object)array('op' => '|')); 57 $this->fail(); 58 } catch (\coding_exception $e) { 59 $this->assertStringContainsString('missing ->show', $e->getMessage()); 60 } 61 try { 62 new tree((object)array('op' => '|', 'show' => 0)); 63 $this->fail(); 64 } catch (\coding_exception $e) { 65 $this->assertStringContainsString('->show not bool', $e->getMessage()); 66 } 67 try { 68 new tree((object)array('op' => '&')); 69 $this->fail(); 70 } catch (\coding_exception $e) { 71 $this->assertStringContainsString('missing ->showc', $e->getMessage()); 72 } 73 try { 74 new tree((object)array('op' => '&', 'showc' => 0)); 75 $this->fail(); 76 } catch (\coding_exception $e) { 77 $this->assertStringContainsString('->showc not array', $e->getMessage()); 78 } 79 try { 80 new tree((object)array('op' => '&', 'showc' => array(0))); 81 $this->fail(); 82 } catch (\coding_exception $e) { 83 $this->assertStringContainsString('->showc value not bool', $e->getMessage()); 84 } 85 try { 86 new tree((object)array('op' => '|', 'show' => true)); 87 $this->fail(); 88 } catch (\coding_exception $e) { 89 $this->assertStringContainsString('missing ->c', $e->getMessage()); 90 } 91 try { 92 new tree((object)array('op' => '|', 'show' => true, 93 'c' => 'side')); 94 $this->fail(); 95 } catch (\coding_exception $e) { 96 $this->assertStringContainsString('->c not array', $e->getMessage()); 97 } 98 try { 99 new tree((object)array('op' => '|', 'show' => true, 100 'c' => array(3))); 101 $this->fail(); 102 } catch (\coding_exception $e) { 103 $this->assertStringContainsString('child not object', $e->getMessage()); 104 } 105 try { 106 new tree((object)array('op' => '|', 'show' => true, 107 'c' => array((object)array('type' => 'doesnotexist')))); 108 $this->fail(); 109 } catch (\coding_exception $e) { 110 $this->assertStringContainsString('Unknown condition type: doesnotexist', $e->getMessage()); 111 } 112 try { 113 new tree((object)array('op' => '|', 'show' => true, 114 'c' => array((object)array()))); 115 $this->fail(); 116 } catch (\coding_exception $e) { 117 $this->assertStringContainsString('missing ->op', $e->getMessage()); 118 } 119 try { 120 new tree((object)array('op' => '&', 121 'c' => array((object)array('op' => '&', 'c' => array())), 122 'showc' => array(true, true) 123 )); 124 $this->fail(); 125 } catch (\coding_exception $e) { 126 $this->assertStringContainsString('->c, ->showc mismatch', $e->getMessage()); 127 } 128 } 129 130 /** 131 * Tests constructing a tree with plugin that does not exist (ignored). 132 */ 133 public function test_construct_ignore_missing_plugin() { 134 // Construct a tree with & combination of one condition that doesn't exist. 135 $tree = new tree(tree::get_root_json(array( 136 (object)array('type' => 'doesnotexist')), tree::OP_OR), true); 137 // Expected result is an empty tree with | condition, shown. 138 $this->assertEquals('+|()', (string)$tree); 139 } 140 141 /** 142 * Tests constructing a tree with subtrees using all available operators. 143 */ 144 public function test_construct_just_trees() { 145 $structure = tree::get_root_json(array( 146 tree::get_nested_json(array(), tree::OP_OR), 147 tree::get_nested_json(array( 148 tree::get_nested_json(array(), tree::OP_NOT_OR)), tree::OP_NOT_AND)), 149 tree::OP_AND, array(true, true)); 150 $tree = new tree($structure); 151 $this->assertEquals('&(+|(),+!&(!|()))', (string)$tree); 152 } 153 154 /** 155 * Tests constructing tree using the mock plugin. 156 */ 157 public function test_construct_with_mock_plugin() { 158 $structure = tree::get_root_json(array( 159 self::mock(array('a' => true, 'm' => ''))), tree::OP_OR); 160 $tree = new tree($structure); 161 $this->assertEquals('+|({mock:y,})', (string)$tree); 162 } 163 164 /** 165 * Tests the check_available and get_result_information functions. 166 */ 167 public function test_check_available() { 168 global $USER; 169 170 // Setup. 171 $this->resetAfterTest(); 172 $info = new \core_availability\mock_info(); 173 $this->setAdminUser(); 174 $information = ''; 175 176 // No conditions. 177 $structure = tree::get_root_json(array(), tree::OP_OR); 178 list ($available, $information) = $this->get_available_results( 179 $structure, $info, $USER->id); 180 $this->assertTrue($available); 181 182 // One condition set to yes. 183 $structure->c = array( 184 self::mock(array('a' => true))); 185 list ($available, $information) = $this->get_available_results( 186 $structure, $info, $USER->id); 187 $this->assertTrue($available); 188 189 // One condition set to no. 190 $structure->c = array( 191 self::mock(array('a' => false, 'm' => 'no'))); 192 list ($available, $information) = $this->get_available_results( 193 $structure, $info, $USER->id); 194 $this->assertFalse($available); 195 $this->assertEquals('SA: no', $information); 196 197 // Two conditions, OR, resolving as true. 198 $structure->c = array( 199 self::mock(array('a' => false, 'm' => 'no')), 200 self::mock(array('a' => true))); 201 list ($available, $information) = $this->get_available_results( 202 $structure, $info, $USER->id); 203 $this->assertTrue($available); 204 $this->assertEquals('', $information); 205 206 // Two conditions, OR, resolving as false. 207 $structure->c = array( 208 self::mock(array('a' => false, 'm' => 'no')), 209 self::mock(array('a' => false, 'm' => 'way'))); 210 list ($available, $information) = $this->get_available_results( 211 $structure, $info, $USER->id); 212 $this->assertFalse($available); 213 $this->assertMatchesRegularExpression('~any of.*no.*way~', $information); 214 215 // Two conditions, OR, resolving as false, no display. 216 $structure->show = false; 217 list ($available, $information) = $this->get_available_results( 218 $structure, $info, $USER->id); 219 $this->assertFalse($available); 220 $this->assertEquals('', $information); 221 222 // Two conditions, AND, resolving as true. 223 $structure->op = '&'; 224 unset($structure->show); 225 $structure->showc = array(true, true); 226 $structure->c = array( 227 self::mock(array('a' => true)), 228 self::mock(array('a' => true))); 229 list ($available, $information) = $this->get_available_results( 230 $structure, $info, $USER->id); 231 $this->assertTrue($available); 232 233 // Two conditions, AND, one false. 234 $structure->c = array( 235 self::mock(array('a' => false, 'm' => 'wom')), 236 self::mock(array('a' => true, 'm' => ''))); 237 list ($available, $information) = $this->get_available_results( 238 $structure, $info, $USER->id); 239 $this->assertFalse($available); 240 $this->assertEquals('SA: wom', $information); 241 242 // Two conditions, AND, both false. 243 $structure->c = array( 244 self::mock(array('a' => false, 'm' => 'wom')), 245 self::mock(array('a' => false, 'm' => 'bat'))); 246 list ($available, $information) = $this->get_available_results( 247 $structure, $info, $USER->id); 248 $this->assertFalse($available); 249 $this->assertMatchesRegularExpression('~wom.*bat~', $information); 250 251 // Two conditions, AND, both false, show turned off for one. When 252 // show is turned off, that means if you don't have that condition 253 // you don't get to see anything at all. 254 $structure->showc[0] = false; 255 list ($available, $information) = $this->get_available_results( 256 $structure, $info, $USER->id); 257 $this->assertFalse($available); 258 $this->assertEquals('', $information); 259 $structure->showc[0] = true; 260 261 // Two conditions, NOT OR, both false. 262 $structure->op = '!|'; 263 list ($available, $information) = $this->get_available_results( 264 $structure, $info, $USER->id); 265 $this->assertTrue($available); 266 267 // Two conditions, NOT OR, one true. 268 $structure->c[0]->a = true; 269 list ($available, $information) = $this->get_available_results( 270 $structure, $info, $USER->id); 271 $this->assertFalse($available); 272 $this->assertEquals('SA: !wom', $information); 273 274 // Two conditions, NOT OR, both true. 275 $structure->c[1]->a = true; 276 list ($available, $information) = $this->get_available_results( 277 $structure, $info, $USER->id); 278 $this->assertFalse($available); 279 $this->assertMatchesRegularExpression('~!wom.*!bat~', $information); 280 281 // Two conditions, NOT AND, both true. 282 $structure->op = '!&'; 283 unset($structure->showc); 284 $structure->show = true; 285 list ($available, $information) = $this->get_available_results( 286 $structure, $info, $USER->id); 287 $this->assertFalse($available); 288 $this->assertMatchesRegularExpression('~any of.*!wom.*!bat~', $information); 289 290 // Two conditions, NOT AND, one true. 291 $structure->c[1]->a = false; 292 list ($available, $information) = $this->get_available_results( 293 $structure, $info, $USER->id); 294 $this->assertTrue($available); 295 296 // Nested NOT conditions; true. 297 $structure->c = array( 298 tree::get_nested_json(array( 299 self::mock(array('a' => true, 'm' => 'no'))), tree::OP_NOT_AND)); 300 list ($available, $information) = $this->get_available_results( 301 $structure, $info, $USER->id); 302 $this->assertTrue($available); 303 304 // Nested NOT conditions; false (note no ! in message). 305 $structure->c[0]->c[0]->a = false; 306 list ($available, $information) = $this->get_available_results( 307 $structure, $info, $USER->id); 308 $this->assertFalse($available); 309 $this->assertEquals('SA: no', $information); 310 311 // Nested condition groups, message test. 312 $structure->op = '|'; 313 $structure->c = array( 314 tree::get_nested_json(array( 315 self::mock(array('a' => false, 'm' => '1')), 316 self::mock(array('a' => false, 'm' => '2')) 317 ), tree::OP_AND), 318 self::mock(array('a' => false, 'm' => 3))); 319 list ($available, $information) = $this->get_available_results( 320 $structure, $info, $USER->id); 321 $this->assertFalse($available); 322 $this->assertMatchesRegularExpression('~<ul.*<ul.*<li.*1.*<li.*2.*</ul>.*<li.*3~', $information); 323 } 324 325 /** 326 * Shortcut function to check availability and also get information. 327 * 328 * @param \stdClass $structure Tree structure 329 * @param \core_availability\info $info Location info 330 * @param int $userid User id 331 */ 332 protected function get_available_results($structure, \core_availability\info $info, $userid) { 333 global $PAGE, $OUTPUT; 334 $tree = new tree($structure); 335 $result = $tree->check_available(false, $info, true, $userid); 336 $information = $tree->get_result_information($info, $result); 337 if (!is_string($information)) { 338 $renderable = new \core_availability\output\availability_info($information); 339 $information = str_replace(array("\r", "\n"), '', $OUTPUT->render($renderable)); 340 } 341 return array($result->is_available(), $information); 342 } 343 344 /** 345 * Shortcut function to render the full availability information. 346 * 347 * @param \stdClass $structure Tree structure 348 * @param \core_availability\info $info Location info 349 */ 350 protected function render_full_information($structure, \core_availability\info $info) { 351 global $OUTPUT; 352 $tree = new tree($structure); 353 $information = $tree->get_full_information($info); 354 $renderable = new \core_availability\output\availability_info($information); 355 $html = $OUTPUT->render($renderable); 356 return str_replace(array("\r", "\n"), '', $html); 357 } 358 359 /** 360 * Tests the is_available_for_all() function. 361 */ 362 public function test_is_available_for_all() { 363 // Empty tree is always available. 364 $structure = tree::get_root_json(array(), tree::OP_OR); 365 $tree = new tree($structure); 366 $this->assertTrue($tree->is_available_for_all()); 367 368 // Tree with normal item in it, not always available. 369 $structure->c[0] = (object)array('type' => 'mock'); 370 $tree = new tree($structure); 371 $this->assertFalse($tree->is_available_for_all()); 372 373 // OR tree with one always-available item. 374 $structure->c[1] = self::mock(array('all' => true)); 375 $tree = new tree($structure); 376 $this->assertTrue($tree->is_available_for_all()); 377 378 // AND tree with one always-available and one not. 379 $structure->op = '&'; 380 $structure->showc = array(true, true); 381 unset($structure->show); 382 $tree = new tree($structure); 383 $this->assertFalse($tree->is_available_for_all()); 384 385 // Test NOT conditions (items not always-available). 386 $structure->op = '!&'; 387 $structure->show = true; 388 unset($structure->showc); 389 $tree = new tree($structure); 390 $this->assertFalse($tree->is_available_for_all()); 391 392 // Test again with one item always-available for NOT mode. 393 $structure->c[1]->allnot = true; 394 $tree = new tree($structure); 395 $this->assertTrue($tree->is_available_for_all()); 396 } 397 398 /** 399 * Tests the get_full_information() function. 400 */ 401 public function test_get_full_information() { 402 global $PAGE; 403 // Setup. 404 $info = new \core_availability\mock_info(); 405 406 // No conditions. 407 $structure = tree::get_root_json(array(), tree::OP_OR); 408 $tree = new tree($structure); 409 $this->assertEquals('', $tree->get_full_information($info)); 410 411 // Condition (normal and NOT). 412 $structure->c = array( 413 self::mock(array('m' => 'thing'))); 414 $tree = new tree($structure); 415 $this->assertEquals('SA: [FULL]thing', 416 $tree->get_full_information($info)); 417 $structure->op = '!&'; 418 $tree = new tree($structure); 419 $this->assertEquals('SA: ![FULL]thing', 420 $tree->get_full_information($info)); 421 422 // Complex structure. 423 $structure->op = '|'; 424 $structure->c = array( 425 tree::get_nested_json(array( 426 self::mock(array('m' => '1')), 427 self::mock(array('m' => '2'))), tree::OP_AND), 428 self::mock(array('m' => 3))); 429 $this->assertMatchesRegularExpression('~<ul.*<ul.*<li.*1.*<li.*2.*</ul>.*<li.*3~', 430 $this->render_full_information($structure, $info)); 431 432 // Test intro messages before list. First, OR message. 433 $structure->c = array( 434 self::mock(array('m' => '1')), 435 self::mock(array('m' => '2')) 436 ); 437 $this->assertMatchesRegularExpression('~Not available unless any of:.*<ul~', 438 $this->render_full_information($structure, $info)); 439 440 // Now, OR message when not shown. 441 $structure->show = false; 442 443 $this->assertMatchesRegularExpression('~hidden.*<ul~', 444 $this->render_full_information($structure, $info)); 445 446 // AND message. 447 $structure->op = '&'; 448 unset($structure->show); 449 $structure->showc = array(false, false); 450 $this->assertMatchesRegularExpression('~Not available unless:.*<ul~', 451 $this->render_full_information($structure, $info)); 452 453 // Hidden markers on items. 454 $this->assertMatchesRegularExpression('~1.*hidden.*2.*hidden~', 455 $this->render_full_information($structure, $info)); 456 457 // Hidden markers on child tree and items. 458 $structure->c[1] = tree::get_nested_json(array( 459 self::mock(array('m' => '2')), 460 self::mock(array('m' => '3'))), tree::OP_AND); 461 $this->assertMatchesRegularExpression('~1.*hidden.*All of \(hidden.*2.*3~', 462 $this->render_full_information($structure, $info)); 463 $structure->c[1]->op = '|'; 464 $this->assertMatchesRegularExpression('~1.*hidden.*Any of \(hidden.*2.*3~', 465 $this->render_full_information($structure, $info)); 466 467 // Hidden markers on single-item display, AND and OR. 468 $structure->showc = array(false); 469 $structure->c = array( 470 self::mock(array('m' => '1')) 471 ); 472 $tree = new tree($structure); 473 $this->assertMatchesRegularExpression('~1.*hidden~', 474 $tree->get_full_information($info)); 475 476 unset($structure->showc); 477 $structure->show = false; 478 $structure->op = '|'; 479 $tree = new tree($structure); 480 $this->assertMatchesRegularExpression('~1.*hidden~', 481 $tree->get_full_information($info)); 482 483 // Hidden marker if single item is tree. 484 $structure->c[0] = tree::get_nested_json(array( 485 self::mock(array('m' => '1')), 486 self::mock(array('m' => '2'))), tree::OP_AND); 487 $this->assertMatchesRegularExpression('~Not available \(hidden.*1.*2~', 488 $this->render_full_information($structure, $info)); 489 490 // Single item tree containing single item. 491 unset($structure->c[0]->c[1]); 492 $tree = new tree($structure); 493 $this->assertMatchesRegularExpression('~SA.*1.*hidden~', 494 $tree->get_full_information($info)); 495 } 496 497 /** 498 * Tests the is_empty() function. 499 */ 500 public function test_is_empty() { 501 // Tree with nothing in should be empty. 502 $structure = tree::get_root_json(array(), tree::OP_OR); 503 $tree = new tree($structure); 504 $this->assertTrue($tree->is_empty()); 505 506 // Tree with something in is not empty. 507 $structure = tree::get_root_json(array(self::mock(array('m' => '1'))), tree::OP_OR); 508 $tree = new tree($structure); 509 $this->assertFalse($tree->is_empty()); 510 } 511 512 /** 513 * Tests the get_all_children() function. 514 */ 515 public function test_get_all_children() { 516 // Create a tree with nothing in. 517 $structure = tree::get_root_json(array(), tree::OP_OR); 518 $tree1 = new tree($structure); 519 520 // Create second tree with complex structure. 521 $structure->c = array( 522 tree::get_nested_json(array( 523 self::mock(array('m' => '1')), 524 self::mock(array('m' => '2')) 525 ), tree::OP_OR), 526 self::mock(array('m' => 3))); 527 $tree2 = new tree($structure); 528 529 // Check list of conditions from both trees. 530 $this->assertEquals(array(), $tree1->get_all_children('core_availability\condition')); 531 $result = $tree2->get_all_children('core_availability\condition'); 532 $this->assertEquals(3, count($result)); 533 $this->assertEquals('{mock:n,1}', (string)$result[0]); 534 $this->assertEquals('{mock:n,2}', (string)$result[1]); 535 $this->assertEquals('{mock:n,3}', (string)$result[2]); 536 537 // Check specific type, should give same results. 538 $result2 = $tree2->get_all_children('availability_mock\condition'); 539 $this->assertEquals($result, $result2); 540 } 541 542 /** 543 * Tests the update_dependency_id() function. 544 */ 545 public function test_update_dependency_id() { 546 // Create tree with structure of 3 mocks. 547 $structure = tree::get_root_json(array( 548 tree::get_nested_json(array( 549 self::mock(array('table' => 'frogs', 'id' => 9)), 550 self::mock(array('table' => 'zombies', 'id' => 9)) 551 )), 552 self::mock(array('table' => 'frogs', 'id' => 9)))); 553 554 // Get 'before' value. 555 $tree = new tree($structure); 556 $before = $tree->save(); 557 558 // Try replacing a table or id that isn't used. 559 $this->assertFalse($tree->update_dependency_id('toads', 9, 13)); 560 $this->assertFalse($tree->update_dependency_id('frogs', 7, 8)); 561 $this->assertEquals($before, $tree->save()); 562 563 // Replace the zombies one. 564 $this->assertTrue($tree->update_dependency_id('zombies', 9, 666)); 565 $after = $tree->save(); 566 $this->assertEquals(666, $after->c[0]->c[1]->id); 567 568 // And the frogs one. 569 $this->assertTrue($tree->update_dependency_id('frogs', 9, 3)); 570 $after = $tree->save(); 571 $this->assertEquals(3, $after->c[0]->c[0]->id); 572 $this->assertEquals(3, $after->c[1]->id); 573 } 574 575 /** 576 * Tests the filter_users function. 577 */ 578 public function test_filter_users() { 579 $info = new \core_availability\mock_info(); 580 $checker = new capability_checker($info->get_context()); 581 582 // Don't need to create real users in database, just use these ids. 583 $users = array(1 => null, 2 => null, 3 => null); 584 585 // Test basic tree with one condition that doesn't filter. 586 $structure = tree::get_root_json(array(self::mock(array()))); 587 $tree = new tree($structure); 588 $result = $tree->filter_user_list($users, false, $info, $checker); 589 ksort($result); 590 $this->assertEquals(array(1, 2, 3), array_keys($result)); 591 592 // Now a tree with one condition that filters. 593 $structure = tree::get_root_json(array(self::mock(array('filter' => array(2, 3))))); 594 $tree = new tree($structure); 595 $result = $tree->filter_user_list($users, false, $info, $checker); 596 ksort($result); 597 $this->assertEquals(array(2, 3), array_keys($result)); 598 599 // Tree with two conditions that both filter (|). 600 $structure = tree::get_root_json(array( 601 self::mock(array('filter' => array(3))), 602 self::mock(array('filter' => array(1)))), tree::OP_OR); 603 $tree = new tree($structure); 604 $result = $tree->filter_user_list($users, false, $info, $checker); 605 ksort($result); 606 $this->assertEquals(array(1, 3), array_keys($result)); 607 608 // Tree with OR condition one of which doesn't filter. 609 $structure = tree::get_root_json(array( 610 self::mock(array('filter' => array(3))), 611 self::mock(array())), tree::OP_OR); 612 $tree = new tree($structure); 613 $result = $tree->filter_user_list($users, false, $info, $checker); 614 ksort($result); 615 $this->assertEquals(array(1, 2, 3), array_keys($result)); 616 617 // Tree with two condition that both filter (&). 618 $structure = tree::get_root_json(array( 619 self::mock(array('filter' => array(2, 3))), 620 self::mock(array('filter' => array(1, 2))))); 621 $tree = new tree($structure); 622 $result = $tree->filter_user_list($users, false, $info, $checker); 623 ksort($result); 624 $this->assertEquals(array(2), array_keys($result)); 625 626 // Tree with child tree with NOT condition. 627 $structure = tree::get_root_json(array( 628 tree::get_nested_json(array( 629 self::mock(array('filter' => array(1)))), tree::OP_NOT_AND))); 630 $tree = new tree($structure); 631 $result = $tree->filter_user_list($users, false, $info, $checker); 632 ksort($result); 633 $this->assertEquals(array(2, 3), array_keys($result)); 634 } 635 636 /** 637 * Tests the get_json methods in tree (which are mainly for use in testing 638 * but might be used elsewhere). 639 */ 640 public function test_get_json() { 641 // Create a simple child object (fake). 642 $child = (object)array('type' => 'fake'); 643 $childstr = json_encode($child); 644 645 // Minimal case. 646 $this->assertEquals( 647 (object)array('op' => '&', 'c' => array()), 648 tree::get_nested_json(array())); 649 // Children and different operator. 650 $this->assertEquals( 651 (object)array('op' => '|', 'c' => array($child, $child)), 652 tree::get_nested_json(array($child, $child), tree::OP_OR)); 653 654 // Root empty. 655 $this->assertEquals('{"op":"&","c":[],"showc":[]}', 656 json_encode(tree::get_root_json(array(), tree::OP_AND))); 657 // Root with children (multi-show operator). 658 $this->assertEquals('{"op":"&","c":[' . $childstr . ',' . $childstr . 659 '],"showc":[true,true]}', 660 json_encode(tree::get_root_json(array($child, $child), tree::OP_AND))); 661 // Root with children (single-show operator). 662 $this->assertEquals('{"op":"|","c":[' . $childstr . ',' . $childstr . 663 '],"show":true}', 664 json_encode(tree::get_root_json(array($child, $child), tree::OP_OR))); 665 // Root with children (specified show boolean). 666 $this->assertEquals('{"op":"&","c":[' . $childstr . ',' . $childstr . 667 '],"showc":[false,false]}', 668 json_encode(tree::get_root_json(array($child, $child), tree::OP_AND, false))); 669 // Root with children (specified show array). 670 $this->assertEquals('{"op":"&","c":[' . $childstr . ',' . $childstr . 671 '],"showc":[true,false]}', 672 json_encode(tree::get_root_json(array($child, $child), tree::OP_AND, array(true, false)))); 673 } 674 675 /** 676 * Tests the behaviour of the counter in unique_sql_parameter(). 677 * 678 * There was a problem with static counters used to implement a sequence of 679 * parameter placeholders (MDL-53481). As always with static variables, it 680 * is a bit tricky to unit test the behaviour reliably as it depends on the 681 * actual tests executed and also their order. 682 * 683 * To minimise risk of false expected behaviour, this test method should be 684 * first one where {@link core_availability\tree::get_user_list_sql()} is 685 * used. We also use higher number of condition instances to increase the 686 * risk of the counter collision, should there remain a problem. 687 */ 688 public function test_unique_sql_parameter_behaviour() { 689 global $DB; 690 $this->resetAfterTest(); 691 $generator = $this->getDataGenerator(); 692 693 // Create a test course with multiple groupings and groups and a student in each of them. 694 $course = $generator->create_course(); 695 $user = $generator->create_user(); 696 $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student')); 697 $generator->enrol_user($user->id, $course->id, $studentroleid); 698 // The total number of groupings and groups must not be greater than 61. 699 // There is a limit in MySQL on the max number of joined tables. 700 $groups = []; 701 for ($i = 0; $i < 25; $i++) { 702 $group = $generator->create_group(array('courseid' => $course->id)); 703 groups_add_member($group, $user); 704 $groups[] = $group; 705 } 706 $groupings = []; 707 for ($i = 0; $i < 25; $i++) { 708 $groupings[] = $generator->create_grouping(array('courseid' => $course->id)); 709 } 710 foreach ($groupings as $grouping) { 711 foreach ($groups as $group) { 712 groups_assign_grouping($grouping->id, $group->id); 713 } 714 } 715 $info = new \core_availability\mock_info($course); 716 717 // Make a huge tree with 'AND' of all groups and groupings conditions. 718 $conditions = []; 719 foreach ($groups as $group) { 720 $conditions[] = \availability_group\condition::get_json($group->id); 721 } 722 foreach ($groupings as $groupingid) { 723 $conditions[] = \availability_grouping\condition::get_json($grouping->id); 724 } 725 shuffle($conditions); 726 $tree = new tree(tree::get_root_json($conditions)); 727 list($sql, $params) = $tree->get_user_list_sql(false, $info, false); 728 // This must not throw exception. 729 $DB->fix_sql_params($sql, $params); 730 } 731 732 /** 733 * Tests get_user_list_sql. 734 */ 735 public function test_get_user_list_sql() { 736 global $DB; 737 $this->resetAfterTest(); 738 $generator = $this->getDataGenerator(); 739 740 // Create a test course with 2 groups and users in each combination of them. 741 $course = $generator->create_course(); 742 $group1 = $generator->create_group(array('courseid' => $course->id)); 743 $group2 = $generator->create_group(array('courseid' => $course->id)); 744 $userin1 = $generator->create_user(); 745 $userin2 = $generator->create_user(); 746 $userinboth = $generator->create_user(); 747 $userinneither = $generator->create_user(); 748 $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student')); 749 foreach (array($userin1, $userin2, $userinboth, $userinneither) as $user) { 750 $generator->enrol_user($user->id, $course->id, $studentroleid); 751 } 752 groups_add_member($group1, $userin1); 753 groups_add_member($group2, $userin2); 754 groups_add_member($group1, $userinboth); 755 groups_add_member($group2, $userinboth); 756 $info = new \core_availability\mock_info($course); 757 758 // Tree with single group condition. 759 $tree = new tree(tree::get_root_json(array( 760 \availability_group\condition::get_json($group1->id) 761 ))); 762 list($sql, $params) = $tree->get_user_list_sql(false, $info, false); 763 $result = $DB->get_fieldset_sql($sql, $params); 764 sort($result); 765 $this->assertEquals(array($userin1->id, $userinboth->id), $result); 766 767 // Tree with 'AND' of both group conditions. 768 $tree = new tree(tree::get_root_json(array( 769 \availability_group\condition::get_json($group1->id), 770 \availability_group\condition::get_json($group2->id) 771 ))); 772 list($sql, $params) = $tree->get_user_list_sql(false, $info, false); 773 $result = $DB->get_fieldset_sql($sql, $params); 774 sort($result); 775 $this->assertEquals(array($userinboth->id), $result); 776 777 // Tree with 'AND' of both group conditions. 778 $tree = new tree(tree::get_root_json(array( 779 \availability_group\condition::get_json($group1->id), 780 \availability_group\condition::get_json($group2->id) 781 ), tree::OP_OR)); 782 list($sql, $params) = $tree->get_user_list_sql(false, $info, false); 783 $result = $DB->get_fieldset_sql($sql, $params); 784 sort($result); 785 $this->assertEquals(array($userin1->id, $userin2->id, $userinboth->id), $result); 786 787 // Check with flipped logic (NOT above level of tree). 788 list($sql, $params) = $tree->get_user_list_sql(true, $info, false); 789 $result = $DB->get_fieldset_sql($sql, $params); 790 sort($result); 791 $this->assertEquals(array($userinneither->id), $result); 792 793 // Tree with 'OR' of group conditions and a non-filtering condition. 794 // The non-filtering condition should mean that ALL users are included. 795 $tree = new tree(tree::get_root_json(array( 796 \availability_group\condition::get_json($group1->id), 797 \availability_date\condition::get_json(\availability_date\condition::DIRECTION_UNTIL, 3) 798 ), tree::OP_OR)); 799 list($sql, $params) = $tree->get_user_list_sql(false, $info, false); 800 $this->assertEquals('', $sql); 801 $this->assertEquals(array(), $params); 802 } 803 804 /** 805 * Utility function to build the PHP structure representing a mock condition. 806 * 807 * @param array $params Mock parameters 808 * @return \stdClass Structure object 809 */ 810 protected static function mock(array $params) { 811 $params['type'] = 'mock'; 812 return (object)$params; 813 } 814 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body