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