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/** 18 * Test for various bits of datalib.php. 19 * 20 * @package core 21 * @category phpunit 22 * @copyright 2012 The Open University 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26defined('MOODLE_INTERNAL') || die(); 27 28 29/** 30 * Test for various bits of datalib.php. 31 * 32 * @package core 33 * @category phpunit 34 * @copyright 2012 The Open University 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37class core_datalib_testcase extends advanced_testcase { 38 protected function normalise_sql($sort) { 39 return preg_replace('~\s+~', ' ', $sort); 40 } 41 42 protected function assert_same_sql($expected, $actual) { 43 $this->assertSame($this->normalise_sql($expected), $this->normalise_sql($actual)); 44 } 45 46 /** 47 * Do a test of the user search SQL with database users. 48 */ 49 public function test_users_search_sql() { 50 global $DB; 51 $this->resetAfterTest(); 52 53 // Set up test users. 54 $user1 = array( 55 'username' => 'usernametest1', 56 'idnumber' => 'idnumbertest1', 57 'firstname' => 'First Name User Test 1', 58 'lastname' => 'Last Name User Test 1', 59 'email' => 'usertest1@example.com', 60 'address' => '2 Test Street Perth 6000 WA', 61 'phone1' => '01010101010', 62 'phone2' => '02020203', 63 'department' => 'Department of user 1', 64 'institution' => 'Institution of user 1', 65 'description' => 'This is a description for user 1', 66 'descriptionformat' => FORMAT_MOODLE, 67 'city' => 'Perth', 68 'country' => 'AU' 69 ); 70 $user1 = self::getDataGenerator()->create_user($user1); 71 $user2 = array( 72 'username' => 'usernametest2', 73 'idnumber' => 'idnumbertest2', 74 'firstname' => 'First Name User Test 2', 75 'lastname' => 'Last Name User Test 2', 76 'email' => 'usertest2@example.com', 77 'address' => '222 Test Street Perth 6000 WA', 78 'phone1' => '01010101010', 79 'phone2' => '02020203', 80 'department' => 'Department of user 2', 81 'institution' => 'Institution of user 2', 82 'description' => 'This is a description for user 2', 83 'descriptionformat' => FORMAT_MOODLE, 84 'city' => 'Perth', 85 'country' => 'AU' 86 ); 87 $user2 = self::getDataGenerator()->create_user($user2); 88 89 // Search by name (anywhere in text). 90 list($sql, $params) = users_search_sql('User Test 2', ''); 91 $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); 92 $this->assertFalse(array_key_exists($user1->id, $results)); 93 $this->assertTrue(array_key_exists($user2->id, $results)); 94 95 // Search by (most of) full name. 96 list($sql, $params) = users_search_sql('First Name User Test 2 Last Name User', ''); 97 $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); 98 $this->assertFalse(array_key_exists($user1->id, $results)); 99 $this->assertTrue(array_key_exists($user2->id, $results)); 100 101 // Search by name (start of text) valid or not. 102 list($sql, $params) = users_search_sql('User Test 2', '', false); 103 $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); 104 $this->assertEquals(0, count($results)); 105 list($sql, $params) = users_search_sql('First Name User Test 2', '', false); 106 $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); 107 $this->assertFalse(array_key_exists($user1->id, $results)); 108 $this->assertTrue(array_key_exists($user2->id, $results)); 109 110 // Search by extra fields included or not (address). 111 list($sql, $params) = users_search_sql('Test Street', '', true); 112 $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); 113 $this->assertCount(0, $results); 114 list($sql, $params) = users_search_sql('Test Street', '', true, array('address')); 115 $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); 116 $this->assertCount(2, $results); 117 118 // Exclude user. 119 list($sql, $params) = users_search_sql('User Test', '', true, array(), array($user1->id)); 120 $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); 121 $this->assertFalse(array_key_exists($user1->id, $results)); 122 $this->assertTrue(array_key_exists($user2->id, $results)); 123 124 // Include only user. 125 list($sql, $params) = users_search_sql('User Test', '', true, array(), array(), array($user1->id)); 126 $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); 127 $this->assertTrue(array_key_exists($user1->id, $results)); 128 $this->assertFalse(array_key_exists($user2->id, $results)); 129 130 // Join with another table and use different prefix. 131 set_user_preference('amphibian', 'frog', $user1); 132 set_user_preference('amphibian', 'salamander', $user2); 133 list($sql, $params) = users_search_sql('User Test 1', 'qq'); 134 $results = $DB->get_records_sql(" 135 SELECT up.id, up.value 136 FROM {user} qq 137 JOIN {user_preferences} up ON up.userid = qq.id 138 WHERE up.name = :prefname 139 AND $sql", array_merge(array('prefname' => 'amphibian'), $params)); 140 $this->assertEquals(1, count($results)); 141 foreach ($results as $record) { 142 $this->assertSame('frog', $record->value); 143 } 144 145 // Join with another table and include other table fields in search. 146 set_user_preference('reptile', 'snake', $user1); 147 set_user_preference('reptile', 'lizard', $user2); 148 list($sql, $params) = users_search_sql('snake', 'qq', true, ['up.value']); 149 $results = $DB->get_records_sql(" 150 SELECT up.id, up.value 151 FROM {user} qq 152 JOIN {user_preferences} up ON up.userid = qq.id 153 WHERE up.name = :prefname 154 AND $sql", array_merge(array('prefname' => 'reptile'), $params)); 155 $this->assertEquals(1, count($results)); 156 foreach ($results as $record) { 157 $this->assertSame('snake', $record->value); 158 } 159 } 160 161 public function test_users_order_by_sql_simple() { 162 list($sort, $params) = users_order_by_sql(); 163 $this->assert_same_sql('lastname, firstname, id', $sort); 164 $this->assertEquals(array(), $params); 165 } 166 167 public function test_users_order_by_sql_table_prefix() { 168 list($sort, $params) = users_order_by_sql('u'); 169 $this->assert_same_sql('u.lastname, u.firstname, u.id', $sort); 170 $this->assertEquals(array(), $params); 171 } 172 173 public function test_users_order_by_sql_search_no_extra_fields() { 174 global $CFG, $DB; 175 $this->resetAfterTest(true); 176 177 $CFG->showuseridentity = ''; 178 179 list($sort, $params) = users_order_by_sql('', 'search', context_system::instance()); 180 $this->assert_same_sql('CASE WHEN 181 ' . $DB->sql_fullname() . ' = :usersortexact1 OR 182 LOWER(firstname) = LOWER(:usersortexact2) OR 183 LOWER(lastname) = LOWER(:usersortexact3) 184 THEN 0 ELSE 1 END, lastname, firstname, id', $sort); 185 $this->assertEquals(array('usersortexact1' => 'search', 'usersortexact2' => 'search', 186 'usersortexact3' => 'search'), $params); 187 } 188 189 public function test_users_order_by_sql_search_with_extra_fields_and_prefix() { 190 global $CFG, $DB; 191 $this->resetAfterTest(); 192 193 $CFG->showuseridentity = 'email,idnumber'; 194 $this->setAdminUser(); 195 196 list($sort, $params) = users_order_by_sql('u', 'search', context_system::instance()); 197 $this->assert_same_sql('CASE WHEN 198 ' . $DB->sql_fullname('u.firstname', 'u.lastname') . ' = :usersortexact1 OR 199 LOWER(u.firstname) = LOWER(:usersortexact2) OR 200 LOWER(u.lastname) = LOWER(:usersortexact3) OR 201 LOWER(u.email) = LOWER(:usersortexact4) OR 202 LOWER(u.idnumber) = LOWER(:usersortexact5) 203 THEN 0 ELSE 1 END, u.lastname, u.firstname, u.id', $sort); 204 $this->assertEquals(array('usersortexact1' => 'search', 'usersortexact2' => 'search', 205 'usersortexact3' => 'search', 'usersortexact4' => 'search', 'usersortexact5' => 'search'), $params); 206 } 207 208 public function test_users_order_by_sql_search_with_custom_fields(): void { 209 global $CFG, $DB; 210 $this->resetAfterTest(); 211 212 $CFG->showuseridentity = 'email,idnumber'; 213 $this->setAdminUser(); 214 215 list($sort, $params) = 216 users_order_by_sql('u', 'search', context_system::instance(), ['profile_field_customfield' => 'x.customfield']); 217 $this->assert_same_sql('CASE WHEN 218 ' . $DB->sql_fullname('u.firstname', 'u.lastname') . ' = :usersortexact1 OR 219 LOWER(u.firstname) = LOWER(:usersortexact2) OR 220 LOWER(u.lastname) = LOWER(:usersortexact3) OR 221 LOWER(x.customfield) = LOWER(:usersortexact4) 222 THEN 0 ELSE 1 END, u.lastname, u.firstname, u.id', $sort); 223 $this->assertEquals(array('usersortexact1' => 'search', 'usersortexact2' => 'search', 224 'usersortexact3' => 'search', 'usersortexact4' => 'search'), $params); 225 } 226 227 public function test_get_admin() { 228 global $CFG, $DB; 229 $this->resetAfterTest(); 230 231 $this->assertSame('2', $CFG->siteadmins); // Admin always has id 2 in new installs. 232 $defaultadmin = get_admin(); 233 $this->assertEquals($defaultadmin->id, 2); 234 235 unset_config('siteadmins'); 236 $this->assertFalse(get_admin()); 237 238 set_config('siteadmins', -1); 239 $this->assertFalse(get_admin()); 240 241 $user1 = $this->getDataGenerator()->create_user(); 242 $user2 = $this->getDataGenerator()->create_user(); 243 244 set_config('siteadmins', $user1->id.','.$user2->id); 245 $admin = get_admin(); 246 $this->assertEquals($user1->id, $admin->id); 247 248 set_config('siteadmins', '-1,'.$user2->id.','.$user1->id); 249 $admin = get_admin(); 250 $this->assertEquals($user2->id, $admin->id); 251 252 $odlread = $DB->perf_get_reads(); 253 get_admin(); // No DB queries on repeated call expected. 254 get_admin(); 255 get_admin(); 256 $this->assertEquals($odlread, $DB->perf_get_reads()); 257 } 258 259 public function test_get_admins() { 260 global $CFG, $DB; 261 $this->resetAfterTest(); 262 263 $this->assertSame('2', $CFG->siteadmins); // Admin always has id 2 in new installs. 264 265 $user1 = $this->getDataGenerator()->create_user(); 266 $user2 = $this->getDataGenerator()->create_user(); 267 $user3 = $this->getDataGenerator()->create_user(); 268 $user4 = $this->getDataGenerator()->create_user(); 269 270 $admins = get_admins(); 271 $this->assertCount(1, $admins); 272 $admin = reset($admins); 273 $this->assertTrue(isset($admins[$admin->id])); 274 $this->assertEquals(2, $admin->id); 275 276 unset_config('siteadmins'); 277 $this->assertSame(array(), get_admins()); 278 279 set_config('siteadmins', -1); 280 $this->assertSame(array(), get_admins()); 281 282 set_config('siteadmins', '-1,'.$user2->id.','.$user1->id.','.$user3->id); 283 $this->assertEquals(array($user2->id=>$user2, $user1->id=>$user1, $user3->id=>$user3), get_admins()); 284 285 $odlread = $DB->perf_get_reads(); 286 get_admins(); // This should make just one query. 287 $this->assertEquals($odlread+1, $DB->perf_get_reads()); 288 } 289 290 public function test_get_course() { 291 global $DB, $PAGE, $SITE; 292 $this->resetAfterTest(); 293 294 // First test course will be current course ($COURSE). 295 $course1obj = $this->getDataGenerator()->create_course(array('shortname' => 'FROGS')); 296 $PAGE->set_course($course1obj); 297 298 // Second test course is not current course. 299 $course2obj = $this->getDataGenerator()->create_course(array('shortname' => 'ZOMBIES')); 300 301 // Check it does not make any queries when requesting the $COURSE/$SITE. 302 $before = $DB->perf_get_queries(); 303 $result = get_course($course1obj->id); 304 $this->assertEquals($before, $DB->perf_get_queries()); 305 $this->assertSame('FROGS', $result->shortname); 306 $result = get_course($SITE->id); 307 $this->assertEquals($before, $DB->perf_get_queries()); 308 309 // Check it makes 1 query to request other courses. 310 $result = get_course($course2obj->id); 311 $this->assertSame('ZOMBIES', $result->shortname); 312 $this->assertEquals($before + 1, $DB->perf_get_queries()); 313 } 314 315 /** 316 * Test that specifying fields when calling get_courses always returns required fields "id, category, visible" 317 */ 318 public function test_get_courses_with_fields(): void { 319 $this->resetAfterTest(); 320 321 $category = $this->getDataGenerator()->create_category(); 322 $course = $this->getDataGenerator()->create_course(['category' => $category->id]); 323 324 // Specify "id" only. 325 $courses = get_courses($category->id, 'c.sortorder', 'c.id'); 326 $this->assertCount(1, $courses); 327 $this->assertEquals((object) [ 328 'id' => $course->id, 329 'category' => $course->category, 330 'visible' => $course->visible, 331 ], reset($courses)); 332 333 // Specify some optional fields. 334 $courses = get_courses($category->id, 'c.sortorder', 'c.id, c.shortname, c.fullname'); 335 $this->assertCount(1, $courses); 336 $this->assertEquals((object) [ 337 'id' => $course->id, 338 'category' => $course->category, 339 'visible' => $course->visible, 340 'shortname' => $course->shortname, 341 'fullname' => $course->fullname, 342 ], reset($courses)); 343 } 344 345 public function test_increment_revision_number() { 346 global $DB; 347 $this->resetAfterTest(); 348 349 // Use one of the fields that are used with increment_revision_number(). 350 $course1 = $this->getDataGenerator()->create_course(); 351 $course2 = $this->getDataGenerator()->create_course(); 352 $DB->set_field('course', 'cacherev', 1, array()); 353 354 $record1 = $DB->get_record('course', array('id'=>$course1->id)); 355 $record2 = $DB->get_record('course', array('id'=>$course2->id)); 356 $this->assertEquals(1, $record1->cacherev); 357 $this->assertEquals(1, $record2->cacherev); 358 359 // Incrementing some lower value. 360 $this->setCurrentTimeStart(); 361 increment_revision_number('course', 'cacherev', 'id = :id', array('id'=>$course1->id)); 362 $record1 = $DB->get_record('course', array('id'=>$course1->id)); 363 $record2 = $DB->get_record('course', array('id'=>$course2->id)); 364 $this->assertTimeCurrent($record1->cacherev); 365 $this->assertEquals(1, $record2->cacherev); 366 367 // Incrementing in the same second. 368 $rev1 = $DB->get_field('course', 'cacherev', array('id'=>$course1->id)); 369 $now = time(); 370 $DB->set_field('course', 'cacherev', $now, array('id'=>$course1->id)); 371 increment_revision_number('course', 'cacherev', 'id = :id', array('id'=>$course1->id)); 372 $rev2 = $DB->get_field('course', 'cacherev', array('id'=>$course1->id)); 373 $this->assertGreaterThan($rev1, $rev2); 374 increment_revision_number('course', 'cacherev', 'id = :id', array('id'=>$course1->id)); 375 $rev3 = $DB->get_field('course', 'cacherev', array('id'=>$course1->id)); 376 $this->assertGreaterThan($rev2, $rev3); 377 $this->assertGreaterThan($now+1, $rev3); 378 increment_revision_number('course', 'cacherev', 'id = :id', array('id'=>$course1->id)); 379 $rev4 = $DB->get_field('course', 'cacherev', array('id'=>$course1->id)); 380 $this->assertGreaterThan($rev3, $rev4); 381 $this->assertGreaterThan($now+2, $rev4); 382 383 // Recovering from runaway revision. 384 $DB->set_field('course', 'cacherev', time()+60*60*60, array('id'=>$course2->id)); 385 $record2 = $DB->get_record('course', array('id'=>$course2->id)); 386 $this->assertGreaterThan(time(), $record2->cacherev); 387 $this->setCurrentTimeStart(); 388 increment_revision_number('course', 'cacherev', 'id = :id', array('id'=>$course2->id)); 389 $record2b = $DB->get_record('course', array('id'=>$course2->id)); 390 $this->assertTimeCurrent($record2b->cacherev); 391 392 // Update all revisions. 393 $DB->set_field('course', 'cacherev', 1, array()); 394 $this->setCurrentTimeStart(); 395 increment_revision_number('course', 'cacherev', ''); 396 $record1 = $DB->get_record('course', array('id'=>$course1->id)); 397 $record2 = $DB->get_record('course', array('id'=>$course2->id)); 398 $this->assertTimeCurrent($record1->cacherev); 399 $this->assertEquals($record1->cacherev, $record2->cacherev); 400 } 401 402 public function test_get_coursemodule_from_id() { 403 global $CFG; 404 405 $this->resetAfterTest(); 406 $this->setAdminUser(); // Some generators have bogus access control. 407 408 $this->assertFileExists("$CFG->dirroot/mod/folder/lib.php"); 409 $this->assertFileExists("$CFG->dirroot/mod/glossary/lib.php"); 410 411 $course1 = $this->getDataGenerator()->create_course(); 412 $course2 = $this->getDataGenerator()->create_course(); 413 414 $folder1a = $this->getDataGenerator()->create_module('folder', array('course' => $course1, 'section' => 3)); 415 $folder1b = $this->getDataGenerator()->create_module('folder', array('course' => $course1)); 416 $glossary1 = $this->getDataGenerator()->create_module('glossary', array('course' => $course1)); 417 418 $folder2 = $this->getDataGenerator()->create_module('folder', array('course' => $course2)); 419 420 $cm = get_coursemodule_from_id('folder', $folder1a->cmid); 421 $this->assertInstanceOf('stdClass', $cm); 422 $this->assertSame('folder', $cm->modname); 423 $this->assertSame($folder1a->id, $cm->instance); 424 $this->assertSame($folder1a->course, $cm->course); 425 $this->assertObjectNotHasAttribute('sectionnum', $cm); 426 427 $this->assertEquals($cm, get_coursemodule_from_id('', $folder1a->cmid)); 428 $this->assertEquals($cm, get_coursemodule_from_id('folder', $folder1a->cmid, $course1->id)); 429 $this->assertEquals($cm, get_coursemodule_from_id('folder', $folder1a->cmid, 0)); 430 $this->assertFalse(get_coursemodule_from_id('folder', $folder1a->cmid, -10)); 431 432 $cm2 = get_coursemodule_from_id('folder', $folder1a->cmid, 0, true); 433 $this->assertEquals(3, $cm2->sectionnum); 434 unset($cm2->sectionnum); 435 $this->assertEquals($cm, $cm2); 436 437 $this->assertFalse(get_coursemodule_from_id('folder', -11)); 438 439 try { 440 get_coursemodule_from_id('folder', -11, 0, false, MUST_EXIST); 441 $this->fail('dml_missing_record_exception expected'); 442 } catch (moodle_exception $e) { 443 $this->assertInstanceOf('dml_missing_record_exception', $e); 444 } 445 446 try { 447 get_coursemodule_from_id('', -11, 0, false, MUST_EXIST); 448 $this->fail('dml_missing_record_exception expected'); 449 } catch (moodle_exception $e) { 450 $this->assertInstanceOf('dml_missing_record_exception', $e); 451 } 452 453 try { 454 get_coursemodule_from_id('a b', $folder1a->cmid, 0, false, MUST_EXIST); 455 $this->fail('coding_exception expected'); 456 } catch (moodle_exception $e) { 457 $this->assertInstanceOf('coding_exception', $e); 458 } 459 460 try { 461 get_coursemodule_from_id('abc', $folder1a->cmid, 0, false, MUST_EXIST); 462 $this->fail('dml_read_exception expected'); 463 } catch (moodle_exception $e) { 464 $this->assertInstanceOf('dml_read_exception', $e); 465 } 466 } 467 468 public function test_get_coursemodule_from_instance() { 469 global $CFG; 470 471 $this->resetAfterTest(); 472 $this->setAdminUser(); // Some generators have bogus access control. 473 474 $this->assertFileExists("$CFG->dirroot/mod/folder/lib.php"); 475 $this->assertFileExists("$CFG->dirroot/mod/glossary/lib.php"); 476 477 $course1 = $this->getDataGenerator()->create_course(); 478 $course2 = $this->getDataGenerator()->create_course(); 479 480 $folder1a = $this->getDataGenerator()->create_module('folder', array('course' => $course1, 'section' => 3)); 481 $folder1b = $this->getDataGenerator()->create_module('folder', array('course' => $course1)); 482 483 $folder2 = $this->getDataGenerator()->create_module('folder', array('course' => $course2)); 484 485 $cm = get_coursemodule_from_instance('folder', $folder1a->id); 486 $this->assertInstanceOf('stdClass', $cm); 487 $this->assertSame('folder', $cm->modname); 488 $this->assertSame($folder1a->id, $cm->instance); 489 $this->assertSame($folder1a->course, $cm->course); 490 $this->assertObjectNotHasAttribute('sectionnum', $cm); 491 492 $this->assertEquals($cm, get_coursemodule_from_instance('folder', $folder1a->id, $course1->id)); 493 $this->assertEquals($cm, get_coursemodule_from_instance('folder', $folder1a->id, 0)); 494 $this->assertFalse(get_coursemodule_from_instance('folder', $folder1a->id, -10)); 495 496 $cm2 = get_coursemodule_from_instance('folder', $folder1a->id, 0, true); 497 $this->assertEquals(3, $cm2->sectionnum); 498 unset($cm2->sectionnum); 499 $this->assertEquals($cm, $cm2); 500 501 $this->assertFalse(get_coursemodule_from_instance('folder', -11)); 502 503 try { 504 get_coursemodule_from_instance('folder', -11, 0, false, MUST_EXIST); 505 $this->fail('dml_missing_record_exception expected'); 506 } catch (moodle_exception $e) { 507 $this->assertInstanceOf('dml_missing_record_exception', $e); 508 } 509 510 try { 511 get_coursemodule_from_instance('a b', $folder1a->cmid, 0, false, MUST_EXIST); 512 $this->fail('coding_exception expected'); 513 } catch (moodle_exception $e) { 514 $this->assertInstanceOf('coding_exception', $e); 515 } 516 517 try { 518 get_coursemodule_from_instance('', $folder1a->cmid, 0, false, MUST_EXIST); 519 $this->fail('coding_exception expected'); 520 } catch (moodle_exception $e) { 521 $this->assertInstanceOf('coding_exception', $e); 522 } 523 524 try { 525 get_coursemodule_from_instance('abc', $folder1a->cmid, 0, false, MUST_EXIST); 526 $this->fail('dml_read_exception expected'); 527 } catch (moodle_exception $e) { 528 $this->assertInstanceOf('dml_read_exception', $e); 529 } 530 } 531 532 public function test_get_coursemodules_in_course() { 533 global $CFG; 534 535 $this->resetAfterTest(); 536 $this->setAdminUser(); // Some generators have bogus access control. 537 538 $this->assertFileExists("$CFG->dirroot/mod/folder/lib.php"); 539 $this->assertFileExists("$CFG->dirroot/mod/glossary/lib.php"); 540 $this->assertFileExists("$CFG->dirroot/mod/label/lib.php"); 541 542 $course1 = $this->getDataGenerator()->create_course(); 543 $course2 = $this->getDataGenerator()->create_course(); 544 545 $folder1a = $this->getDataGenerator()->create_module('folder', array('course' => $course1, 'section' => 3)); 546 $folder1b = $this->getDataGenerator()->create_module('folder', array('course' => $course1)); 547 $glossary1 = $this->getDataGenerator()->create_module('glossary', array('course' => $course1)); 548 549 $folder2 = $this->getDataGenerator()->create_module('folder', array('course' => $course2)); 550 $glossary2a = $this->getDataGenerator()->create_module('glossary', array('course' => $course2)); 551 $glossary2b = $this->getDataGenerator()->create_module('glossary', array('course' => $course2)); 552 553 $modules = get_coursemodules_in_course('folder', $course1->id); 554 $this->assertCount(2, $modules); 555 556 $cm = $modules[$folder1a->cmid]; 557 $this->assertSame('folder', $cm->modname); 558 $this->assertSame($folder1a->id, $cm->instance); 559 $this->assertSame($folder1a->course, $cm->course); 560 $this->assertObjectNotHasAttribute('sectionnum', $cm); 561 $this->assertObjectNotHasAttribute('revision', $cm); 562 $this->assertObjectNotHasAttribute('display', $cm); 563 564 $cm = $modules[$folder1b->cmid]; 565 $this->assertSame('folder', $cm->modname); 566 $this->assertSame($folder1b->id, $cm->instance); 567 $this->assertSame($folder1b->course, $cm->course); 568 $this->assertObjectNotHasAttribute('sectionnum', $cm); 569 $this->assertObjectNotHasAttribute('revision', $cm); 570 $this->assertObjectNotHasAttribute('display', $cm); 571 572 $modules = get_coursemodules_in_course('folder', $course1->id, 'revision, display'); 573 $this->assertCount(2, $modules); 574 575 $cm = $modules[$folder1a->cmid]; 576 $this->assertSame('folder', $cm->modname); 577 $this->assertSame($folder1a->id, $cm->instance); 578 $this->assertSame($folder1a->course, $cm->course); 579 $this->assertObjectNotHasAttribute('sectionnum', $cm); 580 $this->assertObjectHasAttribute('revision', $cm); 581 $this->assertObjectHasAttribute('display', $cm); 582 583 $modules = get_coursemodules_in_course('label', $course1->id); 584 $this->assertCount(0, $modules); 585 586 try { 587 get_coursemodules_in_course('a b', $course1->id); 588 $this->fail('coding_exception expected'); 589 } catch (moodle_exception $e) { 590 $this->assertInstanceOf('coding_exception', $e); 591 } 592 593 try { 594 get_coursemodules_in_course('abc', $course1->id); 595 $this->fail('dml_read_exception expected'); 596 } catch (moodle_exception $e) { 597 $this->assertInstanceOf('dml_read_exception', $e); 598 } 599 } 600 601 public function test_get_all_instances_in_courses() { 602 global $CFG; 603 604 $this->resetAfterTest(); 605 $this->setAdminUser(); // Some generators have bogus access control. 606 607 $this->assertFileExists("$CFG->dirroot/mod/folder/lib.php"); 608 $this->assertFileExists("$CFG->dirroot/mod/glossary/lib.php"); 609 610 $course1 = $this->getDataGenerator()->create_course(); 611 $course2 = $this->getDataGenerator()->create_course(); 612 $course3 = $this->getDataGenerator()->create_course(); 613 614 $folder1a = $this->getDataGenerator()->create_module('folder', array('course' => $course1, 'section' => 3)); 615 $folder1b = $this->getDataGenerator()->create_module('folder', array('course' => $course1)); 616 $glossary1 = $this->getDataGenerator()->create_module('glossary', array('course' => $course1)); 617 618 $folder2 = $this->getDataGenerator()->create_module('folder', array('course' => $course2)); 619 $glossary2a = $this->getDataGenerator()->create_module('glossary', array('course' => $course2)); 620 $glossary2b = $this->getDataGenerator()->create_module('glossary', array('course' => $course2)); 621 622 $folder3 = $this->getDataGenerator()->create_module('folder', array('course' => $course3)); 623 624 $modules = get_all_instances_in_courses('folder', array($course1->id => $course1, $course2->id => $course2)); 625 $this->assertCount(3, $modules); 626 627 foreach ($modules as $cm) { 628 if ($folder1a->cmid == $cm->coursemodule) { 629 $folder = $folder1a; 630 } else if ($folder1b->cmid == $cm->coursemodule) { 631 $folder = $folder1b; 632 } else if ($folder2->cmid == $cm->coursemodule) { 633 $folder = $folder2; 634 } else { 635 $this->fail('Unexpected cm'. $cm->coursemodule); 636 } 637 $this->assertSame($folder->name, $cm->name); 638 $this->assertSame($folder->course, $cm->course); 639 } 640 641 try { 642 get_all_instances_in_courses('a b', array($course1->id => $course1, $course2->id => $course2)); 643 $this->fail('coding_exception expected'); 644 } catch (moodle_exception $e) { 645 $this->assertInstanceOf('coding_exception', $e); 646 } 647 648 try { 649 get_all_instances_in_courses('', array($course1->id => $course1, $course2->id => $course2)); 650 $this->fail('coding_exception expected'); 651 } catch (moodle_exception $e) { 652 $this->assertInstanceOf('coding_exception', $e); 653 } 654 } 655 656 public function test_get_all_instances_in_course() { 657 global $CFG; 658 659 $this->resetAfterTest(); 660 $this->setAdminUser(); // Some generators have bogus access control. 661 662 $this->assertFileExists("$CFG->dirroot/mod/folder/lib.php"); 663 $this->assertFileExists("$CFG->dirroot/mod/glossary/lib.php"); 664 665 $course1 = $this->getDataGenerator()->create_course(); 666 $course2 = $this->getDataGenerator()->create_course(); 667 $course3 = $this->getDataGenerator()->create_course(); 668 669 $folder1a = $this->getDataGenerator()->create_module('folder', array('course' => $course1, 'section' => 3)); 670 $folder1b = $this->getDataGenerator()->create_module('folder', array('course' => $course1)); 671 $glossary1 = $this->getDataGenerator()->create_module('glossary', array('course' => $course1)); 672 673 $folder2 = $this->getDataGenerator()->create_module('folder', array('course' => $course2)); 674 $glossary2a = $this->getDataGenerator()->create_module('glossary', array('course' => $course2)); 675 $glossary2b = $this->getDataGenerator()->create_module('glossary', array('course' => $course2)); 676 677 $folder3 = $this->getDataGenerator()->create_module('folder', array('course' => $course3)); 678 679 $modules = get_all_instances_in_course('folder', $course1); 680 $this->assertCount(2, $modules); 681 682 foreach ($modules as $cm) { 683 if ($folder1a->cmid == $cm->coursemodule) { 684 $folder = $folder1a; 685 } else if ($folder1b->cmid == $cm->coursemodule) { 686 $folder = $folder1b; 687 } else { 688 $this->fail('Unexpected cm'. $cm->coursemodule); 689 } 690 $this->assertSame($folder->name, $cm->name); 691 $this->assertSame($folder->course, $cm->course); 692 } 693 694 try { 695 get_all_instances_in_course('a b', $course1); 696 $this->fail('coding_exception expected'); 697 } catch (moodle_exception $e) { 698 $this->assertInstanceOf('coding_exception', $e); 699 } 700 701 try { 702 get_all_instances_in_course('', $course1); 703 $this->fail('coding_exception expected'); 704 } catch (moodle_exception $e) { 705 $this->assertInstanceOf('coding_exception', $e); 706 } 707 } 708 709 /** 710 * Test max courses in category 711 */ 712 public function test_max_courses_in_category() { 713 global $CFG; 714 $this->resetAfterTest(); 715 716 // Default settings. 717 $this->assertEquals(MAX_COURSES_IN_CATEGORY, get_max_courses_in_category()); 718 719 // Misc category. 720 $misc = core_course_category::get_default(); 721 $this->assertEquals(MAX_COURSES_IN_CATEGORY, $misc->sortorder); 722 723 $category1 = $this->getDataGenerator()->create_category(); 724 $category2 = $this->getDataGenerator()->create_category(); 725 726 // Check category sort orders. 727 $this->assertEquals(MAX_COURSES_IN_CATEGORY, core_course_category::get($misc->id)->sortorder); 728 $this->assertEquals(MAX_COURSES_IN_CATEGORY * 2, core_course_category::get($category1->id)->sortorder); 729 $this->assertEquals(MAX_COURSES_IN_CATEGORY * 3, core_course_category::get($category2->id)->sortorder); 730 731 // Create courses. 732 $course1 = $this->getDataGenerator()->create_course(['category' => $category1->id]); 733 $course2 = $this->getDataGenerator()->create_course(['category' => $category2->id]); 734 $course3 = $this->getDataGenerator()->create_course(['category' => $category1->id]); 735 $course4 = $this->getDataGenerator()->create_course(['category' => $category2->id]); 736 737 // Check course sort orders. 738 $this->assertEquals(MAX_COURSES_IN_CATEGORY * 2 + 2, get_course($course1->id)->sortorder); 739 $this->assertEquals(MAX_COURSES_IN_CATEGORY * 3 + 2, get_course($course2->id)->sortorder); 740 $this->assertEquals(MAX_COURSES_IN_CATEGORY * 2 + 1, get_course($course3->id)->sortorder); 741 $this->assertEquals(MAX_COURSES_IN_CATEGORY * 3 + 1, get_course($course4->id)->sortorder); 742 743 // Increase max course in category. 744 $CFG->maxcoursesincategory = 20000; 745 $this->assertEquals(20000, get_max_courses_in_category()); 746 747 // The sort order has not yet fixed, these sort orders should be the same as before. 748 // Categories. 749 $this->assertEquals(MAX_COURSES_IN_CATEGORY, core_course_category::get($misc->id)->sortorder); 750 $this->assertEquals(MAX_COURSES_IN_CATEGORY * 2, core_course_category::get($category1->id)->sortorder); 751 $this->assertEquals(MAX_COURSES_IN_CATEGORY * 3, core_course_category::get($category2->id)->sortorder); 752 // Courses in category 1. 753 $this->assertEquals(MAX_COURSES_IN_CATEGORY * 2 + 2, get_course($course1->id)->sortorder); 754 $this->assertEquals(MAX_COURSES_IN_CATEGORY * 2 + 1, get_course($course3->id)->sortorder); 755 // Courses in category 2. 756 $this->assertEquals(MAX_COURSES_IN_CATEGORY * 3 + 2, get_course($course2->id)->sortorder); 757 $this->assertEquals(MAX_COURSES_IN_CATEGORY * 3 + 1, get_course($course4->id)->sortorder); 758 759 // Create new category so that the sort orders are applied. 760 $category3 = $this->getDataGenerator()->create_category(); 761 // Categories. 762 $this->assertEquals(20000, core_course_category::get($misc->id)->sortorder); 763 $this->assertEquals(20000 * 2, core_course_category::get($category1->id)->sortorder); 764 $this->assertEquals(20000 * 3, core_course_category::get($category2->id)->sortorder); 765 $this->assertEquals(20000 * 4, core_course_category::get($category3->id)->sortorder); 766 // Courses in category 1. 767 $this->assertEquals(20000 * 2 + 2, get_course($course1->id)->sortorder); 768 $this->assertEquals(20000 * 2 + 1, get_course($course3->id)->sortorder); 769 // Courses in category 2. 770 $this->assertEquals(20000 * 3 + 2, get_course($course2->id)->sortorder); 771 $this->assertEquals(20000 * 3 + 1, get_course($course4->id)->sortorder); 772 } 773 774 /** 775 * Test debug message for max courses in category 776 */ 777 public function test_debug_max_courses_in_category() { 778 global $CFG; 779 $this->resetAfterTest(); 780 781 // Set to small value so that we can check the debug message. 782 $CFG->maxcoursesincategory = 3; 783 $this->assertEquals(3, get_max_courses_in_category()); 784 785 $category1 = $this->getDataGenerator()->create_category(); 786 787 // There is only one course, no debug message. 788 $this->getDataGenerator()->create_course(['category' => $category1->id]); 789 $this->assertDebuggingNotCalled(); 790 // There are two courses, no debug message. 791 $this->getDataGenerator()->create_course(['category' => $category1->id]); 792 $this->assertDebuggingNotCalled(); 793 // There is debug message when number of courses reaches the maximum number. 794 $this->getDataGenerator()->create_course(['category' => $category1->id]); 795 $this->assertDebuggingCalled("The number of courses (category id: $category1->id) has reached max number of courses " . 796 "in a category (" . get_max_courses_in_category() . "). It will cause a sorting performance issue. " . 797 "Please set higher value for \$CFG->maxcoursesincategory in config.php. " . 798 "Please also make sure \$CFG->maxcoursesincategory * MAX_COURSE_CATEGORIES less than max integer. " . 799 "See tracker issues: MDL-25669 and MDL-69573"); 800 } 801 802 /** 803 * Tests the get_users_listing function. 804 */ 805 public function test_get_users_listing(): void { 806 global $DB; 807 808 $this->resetAfterTest(); 809 810 $generator = $this->getDataGenerator(); 811 812 // Set up profile field. 813 $generator->create_custom_profile_field(['datatype' => 'text', 814 'shortname' => 'specialid', 'name' => 'Special user id']); 815 816 // Set up the show user identity option. 817 set_config('showuseridentity', 'department,profile_field_specialid'); 818 819 // Get all the existing user ids (we're going to remove these from test results). 820 $existingids = array_fill_keys($DB->get_fieldset_select('user', 'id', '1 = 1'), true); 821 822 // Create some test user accounts. 823 $userids = []; 824 foreach (['a', 'b', 'c', 'd'] as $key) { 825 $record = [ 826 'username' => 'user_' . $key, 827 'firstname' => $key . '_first', 828 'lastname' => 'last_' . $key, 829 'department' => 'department_' . $key, 830 'profile_field_specialid' => 'special_' . $key, 831 'lastaccess' => ord($key) 832 ]; 833 $user = $generator->create_user($record); 834 $userids[] = $user->id; 835 } 836 837 // Check default result with no parameters. 838 $results = get_users_listing(); 839 $results = array_diff_key($results, $existingids); 840 841 // It should return all the results in order. 842 $this->assertEquals($userids, array_keys($results)); 843 844 // Results should have some general fields and name fields, check some samples. 845 $this->assertEquals('user_a', $results[$userids[0]]->username); 846 $this->assertEquals('user_a@example.com', $results[$userids[0]]->email); 847 $this->assertEquals(1, $results[$userids[0]]->confirmed); 848 $this->assertEquals('a_first', $results[$userids[0]]->firstname); 849 $this->assertObjectHasAttribute('firstnamephonetic', $results[$userids[0]]); 850 851 // Should not have the custom field or department because no context specified. 852 $this->assertObjectNotHasAttribute('department', $results[$userids[0]]); 853 $this->assertObjectNotHasAttribute('profile_field_specialid', $results[$userids[0]]); 854 855 // Check sorting. 856 $results = get_users_listing('username', 'DESC'); 857 $results = array_diff_key($results, $existingids); 858 $this->assertEquals([$userids[3], $userids[2], $userids[1], $userids[0]], array_keys($results)); 859 860 // Add the options to showuseridentity and check it returns those fields but only if you 861 // specify a context AND have permissions. 862 $results = get_users_listing('lastaccess', 'asc', 0, 0, '', '', '', '', null, 863 \context_system::instance()); 864 $this->assertObjectNotHasAttribute('department', $results[$userids[0]]); 865 $this->assertObjectNotHasAttribute('profile_field_specialid', $results[$userids[0]]); 866 $this->setAdminUser(); 867 $results = get_users_listing('lastaccess', 'asc', 0, 0, '', '', '', '', null, 868 \context_system::instance()); 869 $this->assertEquals('department_a', $results[$userids[0]]->department); 870 $this->assertEquals('special_a', $results[$userids[0]]->profile_field_specialid); 871 872 // Check search (full name, email, username). 873 $results = get_users_listing('lastaccess', 'asc', 0, 0, 'b_first last_b'); 874 $this->assertEquals([$userids[1]], array_keys($results)); 875 $results = get_users_listing('lastaccess', 'asc', 0, 0, 'c@example'); 876 $this->assertEquals([$userids[2]], array_keys($results)); 877 $results = get_users_listing('lastaccess', 'asc', 0, 0, 'user_d'); 878 $this->assertEquals([$userids[3]], array_keys($results)); 879 880 // Check first and last initial restriction (all the test ones have same last initial). 881 $results = get_users_listing('lastaccess', 'asc', 0, 0, '', 'C'); 882 $this->assertEquals([$userids[2]], array_keys($results)); 883 $results = get_users_listing('lastaccess', 'asc', 0, 0, '', '', 'L'); 884 $results = array_diff_key($results, $existingids); 885 $this->assertEquals($userids, array_keys($results)); 886 887 // Check the extra where clause, either with the 'u.' prefix or not. 888 $results = get_users_listing('lastaccess', 'asc', 0, 0, '', '', '', 'id IN (:x,:y)', 889 ['x' => $userids[1], 'y' => $userids[3]]); 890 $results = array_diff_key($results, $existingids); 891 $this->assertEquals([$userids[1], $userids[3]], array_keys($results)); 892 $results = get_users_listing('lastaccess', 'asc', 0, 0, '', '', '', 'u.id IN (:x,:y)', 893 ['x' => $userids[1], 'y' => $userids[3]]); 894 $results = array_diff_key($results, $existingids); 895 $this->assertEquals([$userids[1], $userids[3]], array_keys($results)); 896 } 897} 898