Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [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 /** 18 * core_component related tests. 19 * 20 * @package core 21 * @category test 22 * @copyright 2013 Petr Skoda {@link http://skodak.org} 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 * 25 * @covers \core_component 26 */ 27 class component_test extends advanced_testcase { 28 29 /** 30 * To be changed if number of subsystems increases/decreases, 31 * this is defined here to annoy devs that try to add more without any thinking, 32 * always verify that it does not collide with any existing add-on modules and subplugins!!! 33 */ 34 const SUBSYSTEMCOUNT = 76; 35 36 public function test_get_core_subsystems() { 37 global $CFG; 38 39 $subsystems = core_component::get_core_subsystems(); 40 41 $this->assertCount(self::SUBSYSTEMCOUNT, $subsystems, 'Oh, somebody added or removed a core subsystem, think twice before doing that!'); 42 43 // Make sure all paths are full/null, exist and are inside dirroot. 44 foreach ($subsystems as $subsystem => $fulldir) { 45 $this->assertFalse(strpos($subsystem, '_'), 'Core subsystems must be one work without underscores'); 46 if ($fulldir === null) { 47 if ($subsystem === 'filepicker' or $subsystem === 'help') { 48 // Arrgghh, let's not introduce more subsystems for no real reason... 49 } else { 50 // Lang strings. 51 $this->assertFileExists("$CFG->dirroot/lang/en/$subsystem.php", 'Core subsystems without fulldir are usually used for lang strings.'); 52 } 53 continue; 54 } 55 $this->assertFileExists($fulldir); 56 // Check that base uses realpath() separators and "/" in the subdirs. 57 $this->assertStringStartsWith($CFG->dirroot.'/', $fulldir); 58 $reldir = substr($fulldir, strlen($CFG->dirroot)+1); 59 $this->assertFalse(strpos($reldir, '\\')); 60 } 61 62 // Make sure all core language files are also subsystems! 63 $items = new DirectoryIterator("$CFG->dirroot/lang/en"); 64 foreach ($items as $item) { 65 if ($item->isDot() or $item->isDir()) { 66 continue; 67 } 68 $file = $item->getFilename(); 69 if ($file === 'moodle.php') { 70 // Do not add new lang strings unless really necessary!!! 71 continue; 72 } 73 74 if (substr($file, -4) !== '.php') { 75 continue; 76 } 77 $file = substr($file, 0, strlen($file)-4); 78 $this->assertArrayHasKey($file, $subsystems, 'All core lang files should be subsystems, think twice before adding anything!'); 79 } 80 unset($item); 81 unset($items); 82 83 } 84 85 public function test_deprecated_get_core_subsystems() { 86 global $CFG; 87 88 $subsystems = core_component::get_core_subsystems(); 89 90 $this->assertSame($subsystems, get_core_subsystems(true)); 91 92 $realsubsystems = get_core_subsystems(); 93 $this->assertDebuggingCalled(); 94 $this->assertSame($realsubsystems, get_core_subsystems(false)); 95 $this->assertDebuggingCalled(); 96 97 $this->assertEquals(count($subsystems), count($realsubsystems)); 98 99 foreach ($subsystems as $subsystem => $fulldir) { 100 $this->assertArrayHasKey($subsystem, $realsubsystems); 101 if ($fulldir === null) { 102 $this->assertNull($realsubsystems[$subsystem]); 103 continue; 104 } 105 $this->assertSame($fulldir, $CFG->dirroot.'/'.$realsubsystems[$subsystem]); 106 } 107 } 108 109 public function test_get_plugin_types() { 110 global $CFG; 111 112 $this->assertTrue(empty($CFG->themedir), 'Non-empty $CFG->themedir is not covered by any tests yet, you need to disable it.'); 113 114 $plugintypes = core_component::get_plugin_types(); 115 116 foreach ($plugintypes as $plugintype => $fulldir) { 117 $this->assertStringStartsWith("$CFG->dirroot/", $fulldir); 118 } 119 } 120 121 public function test_deprecated_get_plugin_types() { 122 global $CFG; 123 124 $plugintypes = core_component::get_plugin_types(); 125 126 $this->assertSame($plugintypes, get_plugin_types()); 127 $this->assertSame($plugintypes, get_plugin_types(true)); 128 129 $realplugintypes = get_plugin_types(false); 130 $this->assertDebuggingCalled(); 131 132 foreach ($plugintypes as $plugintype => $fulldir) { 133 $this->assertSame($fulldir, $CFG->dirroot.'/'.$realplugintypes[$plugintype]); 134 } 135 } 136 137 public function test_get_plugin_list() { 138 global $CFG; 139 140 $plugintypes = core_component::get_plugin_types(); 141 142 foreach ($plugintypes as $plugintype => $fulldir) { 143 $plugins = core_component::get_plugin_list($plugintype); 144 foreach ($plugins as $pluginname => $plugindir) { 145 $this->assertStringStartsWith("$CFG->dirroot/", $plugindir); 146 } 147 if ($plugintype !== 'auth') { 148 // Let's crosscheck it with independent implementation (auth/db is an exception). 149 $reldir = substr($fulldir, strlen($CFG->dirroot)+1); 150 $dirs = get_list_of_plugins($reldir); 151 $dirs = array_values($dirs); 152 $this->assertDebuggingCalled(); 153 $this->assertSame($dirs, array_keys($plugins)); 154 } 155 } 156 } 157 158 public function test_deprecated_get_plugin_list() { 159 $plugintypes = core_component::get_plugin_types(); 160 161 foreach ($plugintypes as $plugintype => $fulldir) { 162 $plugins = core_component::get_plugin_list($plugintype); 163 $this->assertSame($plugins, get_plugin_list($plugintype)); 164 } 165 } 166 167 public function test_get_plugin_directory() { 168 $plugintypes = core_component::get_plugin_types(); 169 170 foreach ($plugintypes as $plugintype => $fulldir) { 171 $plugins = core_component::get_plugin_list($plugintype); 172 foreach ($plugins as $pluginname => $plugindir) { 173 $this->assertSame($plugindir, core_component::get_plugin_directory($plugintype, $pluginname)); 174 } 175 } 176 } 177 178 public function test_deprecated_get_plugin_directory() { 179 $plugintypes = core_component::get_plugin_types(); 180 181 foreach ($plugintypes as $plugintype => $fulldir) { 182 $plugins = core_component::get_plugin_list($plugintype); 183 foreach ($plugins as $pluginname => $plugindir) { 184 $this->assertSame(core_component::get_plugin_directory($plugintype, $pluginname), get_plugin_directory($plugintype, $pluginname)); 185 } 186 } 187 } 188 189 public function test_get_subsystem_directory() { 190 $subsystems = core_component::get_core_subsystems(); 191 foreach ($subsystems as $subsystem => $fulldir) { 192 $this->assertSame($fulldir, core_component::get_subsystem_directory($subsystem)); 193 } 194 } 195 196 /** 197 * Test that the get_plugin_list_with_file() function returns the correct list of plugins. 198 * 199 * @covers \core_component::is_valid_plugin_name 200 * @dataProvider is_valid_plugin_name_provider 201 * @param array $arguments 202 * @param bool $expected 203 */ 204 public function test_is_valid_plugin_name(array $arguments, bool $expected): void { 205 $this->assertEquals($expected, core_component::is_valid_plugin_name(...$arguments)); 206 } 207 208 /** 209 * Data provider for the is_valid_plugin_name function. 210 * 211 * @return array 212 */ 213 public function is_valid_plugin_name_provider(): array { 214 return [ 215 [['mod', 'example1'], true], 216 [['mod', 'feedback360'], true], 217 [['mod', 'feedback_360'], false], 218 [['mod', '2feedback'], false], 219 [['mod', '1example'], false], 220 [['mod', 'example.xx'], false], 221 [['mod', '.example'], false], 222 [['mod', '_example'], false], 223 [['mod', 'example_'], false], 224 [['mod', 'example_x1'], false], 225 [['mod', 'example-x1'], false], 226 [['mod', 'role'], false], 227 228 [['tool', 'example1'], true], 229 [['tool', 'example_x1'], true], 230 [['tool', 'example_x1_xxx'], true], 231 [['tool', 'feedback360'], true], 232 [['tool', 'feed_back360'], true], 233 [['tool', 'role'], true], 234 [['tool', '1example'], false], 235 [['tool', 'example.xx'], false], 236 [['tool', 'example-xx'], false], 237 [['tool', '.example'], false], 238 [['tool', '_example'], false], 239 [['tool', 'example_'], false], 240 [['tool', 'example__x1'], false], 241 242 // Some invalid cases. 243 [['mod', null], false], 244 [['mod', ''], false], 245 [['tool', null], false], 246 [['tool', ''], false], 247 ]; 248 } 249 250 public function test_normalize_componentname() { 251 // Moodle core. 252 $this->assertSame('core', core_component::normalize_componentname('core')); 253 $this->assertSame('core', core_component::normalize_componentname('moodle')); 254 $this->assertSame('core', core_component::normalize_componentname('')); 255 256 // Moodle core subsystems. 257 $this->assertSame('core_admin', core_component::normalize_componentname('admin')); 258 $this->assertSame('core_admin', core_component::normalize_componentname('core_admin')); 259 $this->assertSame('core_admin', core_component::normalize_componentname('moodle_admin')); 260 261 // Activity modules and their subplugins. 262 $this->assertSame('mod_workshop', core_component::normalize_componentname('workshop')); 263 $this->assertSame('mod_workshop', core_component::normalize_componentname('mod_workshop')); 264 $this->assertSame('workshopform_accumulative', core_component::normalize_componentname('workshopform_accumulative')); 265 $this->assertSame('mod_quiz', core_component::normalize_componentname('quiz')); 266 $this->assertSame('quiz_grading', core_component::normalize_componentname('quiz_grading')); 267 $this->assertSame('mod_data', core_component::normalize_componentname('data')); 268 $this->assertSame('datafield_checkbox', core_component::normalize_componentname('datafield_checkbox')); 269 270 // Other plugin types. 271 $this->assertSame('auth_mnet', core_component::normalize_componentname('auth_mnet')); 272 $this->assertSame('enrol_self', core_component::normalize_componentname('enrol_self')); 273 $this->assertSame('block_html', core_component::normalize_componentname('block_html')); 274 $this->assertSame('block_mnet_hosts', core_component::normalize_componentname('block_mnet_hosts')); 275 $this->assertSame('local_amos', core_component::normalize_componentname('local_amos')); 276 $this->assertSame('local_admin', core_component::normalize_componentname('local_admin')); 277 278 // Unknown words without underscore are supposed to be activity modules. 279 $this->assertSame('mod_whoonearthwouldcomewithsuchastupidnameofcomponent', 280 core_component::normalize_componentname('whoonearthwouldcomewithsuchastupidnameofcomponent')); 281 // Module names can not contain underscores, this must be a subplugin. 282 $this->assertSame('whoonearth_wouldcomewithsuchastupidnameofcomponent', 283 core_component::normalize_componentname('whoonearth_wouldcomewithsuchastupidnameofcomponent')); 284 $this->assertSame('whoonearth_would_come_withsuchastupidnameofcomponent', 285 core_component::normalize_componentname('whoonearth_would_come_withsuchastupidnameofcomponent')); 286 } 287 288 public function test_normalize_component() { 289 // Moodle core. 290 $this->assertSame(array('core', null), core_component::normalize_component('core')); 291 $this->assertSame(array('core', null), core_component::normalize_component('moodle')); 292 $this->assertSame(array('core', null), core_component::normalize_component('')); 293 294 // Moodle core subsystems. 295 $this->assertSame(array('core', 'admin'), core_component::normalize_component('admin')); 296 $this->assertSame(array('core', 'admin'), core_component::normalize_component('core_admin')); 297 $this->assertSame(array('core', 'admin'), core_component::normalize_component('moodle_admin')); 298 299 // Activity modules and their subplugins. 300 $this->assertSame(array('mod', 'workshop'), core_component::normalize_component('workshop')); 301 $this->assertSame(array('mod', 'workshop'), core_component::normalize_component('mod_workshop')); 302 $this->assertSame(array('workshopform', 'accumulative'), core_component::normalize_component('workshopform_accumulative')); 303 $this->assertSame(array('mod', 'quiz'), core_component::normalize_component('quiz')); 304 $this->assertSame(array('quiz', 'grading'), core_component::normalize_component('quiz_grading')); 305 $this->assertSame(array('mod', 'data'), core_component::normalize_component('data')); 306 $this->assertSame(array('datafield', 'checkbox'), core_component::normalize_component('datafield_checkbox')); 307 308 // Other plugin types. 309 $this->assertSame(array('auth', 'mnet'), core_component::normalize_component('auth_mnet')); 310 $this->assertSame(array('enrol', 'self'), core_component::normalize_component('enrol_self')); 311 $this->assertSame(array('block', 'html'), core_component::normalize_component('block_html')); 312 $this->assertSame(array('block', 'mnet_hosts'), core_component::normalize_component('block_mnet_hosts')); 313 $this->assertSame(array('local', 'amos'), core_component::normalize_component('local_amos')); 314 $this->assertSame(array('local', 'admin'), core_component::normalize_component('local_admin')); 315 316 // Unknown words without underscore are supposed to be activity modules. 317 $this->assertSame(array('mod', 'whoonearthwouldcomewithsuchastupidnameofcomponent'), 318 core_component::normalize_component('whoonearthwouldcomewithsuchastupidnameofcomponent')); 319 // Module names can not contain underscores, this must be a subplugin. 320 $this->assertSame(array('whoonearth', 'wouldcomewithsuchastupidnameofcomponent'), 321 core_component::normalize_component('whoonearth_wouldcomewithsuchastupidnameofcomponent')); 322 $this->assertSame(array('whoonearth', 'would_come_withsuchastupidnameofcomponent'), 323 core_component::normalize_component('whoonearth_would_come_withsuchastupidnameofcomponent')); 324 } 325 326 public function test_deprecated_normalize_component() { 327 // Moodle core. 328 $this->assertSame(array('core', null), normalize_component('core')); 329 $this->assertSame(array('core', null), normalize_component('')); 330 $this->assertSame(array('core', null), normalize_component('moodle')); 331 332 // Moodle core subsystems. 333 $this->assertSame(array('core', 'admin'), normalize_component('admin')); 334 $this->assertSame(array('core', 'admin'), normalize_component('core_admin')); 335 $this->assertSame(array('core', 'admin'), normalize_component('moodle_admin')); 336 337 // Activity modules and their subplugins. 338 $this->assertSame(array('mod', 'workshop'), normalize_component('workshop')); 339 $this->assertSame(array('mod', 'workshop'), normalize_component('mod_workshop')); 340 $this->assertSame(array('workshopform', 'accumulative'), normalize_component('workshopform_accumulative')); 341 $this->assertSame(array('mod', 'quiz'), normalize_component('quiz')); 342 $this->assertSame(array('quiz', 'grading'), normalize_component('quiz_grading')); 343 $this->assertSame(array('mod', 'data'), normalize_component('data')); 344 $this->assertSame(array('datafield', 'checkbox'), normalize_component('datafield_checkbox')); 345 346 // Other plugin types. 347 $this->assertSame(array('auth', 'mnet'), normalize_component('auth_mnet')); 348 $this->assertSame(array('enrol', 'self'), normalize_component('enrol_self')); 349 $this->assertSame(array('block', 'html'), normalize_component('block_html')); 350 $this->assertSame(array('block', 'mnet_hosts'), normalize_component('block_mnet_hosts')); 351 $this->assertSame(array('local', 'amos'), normalize_component('local_amos')); 352 $this->assertSame(array('local', 'admin'), normalize_component('local_admin')); 353 354 // Unknown words without underscore are supposed to be activity modules. 355 $this->assertSame(array('mod', 'whoonearthwouldcomewithsuchastupidnameofcomponent'), 356 normalize_component('whoonearthwouldcomewithsuchastupidnameofcomponent')); 357 // Module names can not contain underscores, this must be a subplugin. 358 $this->assertSame(array('whoonearth', 'wouldcomewithsuchastupidnameofcomponent'), 359 normalize_component('whoonearth_wouldcomewithsuchastupidnameofcomponent')); 360 $this->assertSame(array('whoonearth', 'would_come_withsuchastupidnameofcomponent'), 361 normalize_component('whoonearth_would_come_withsuchastupidnameofcomponent')); 362 } 363 364 public function test_get_component_directory() { 365 $plugintypes = core_component::get_plugin_types(); 366 foreach ($plugintypes as $plugintype => $fulldir) { 367 $plugins = core_component::get_plugin_list($plugintype); 368 foreach ($plugins as $pluginname => $plugindir) { 369 $this->assertSame($plugindir, core_component::get_component_directory(($plugintype.'_'.$pluginname))); 370 } 371 } 372 373 $subsystems = core_component::get_core_subsystems(); 374 foreach ($subsystems as $subsystem => $fulldir) { 375 $this->assertSame($fulldir, core_component::get_component_directory(('core_'.$subsystem))); 376 } 377 } 378 379 public function test_deprecated_get_component_directory() { 380 $plugintypes = core_component::get_plugin_types(); 381 foreach ($plugintypes as $plugintype => $fulldir) { 382 $plugins = core_component::get_plugin_list($plugintype); 383 foreach ($plugins as $pluginname => $plugindir) { 384 $this->assertSame($plugindir, get_component_directory(($plugintype.'_'.$pluginname))); 385 } 386 } 387 388 $subsystems = core_component::get_core_subsystems(); 389 foreach ($subsystems as $subsystem => $fulldir) { 390 $this->assertSame($fulldir, get_component_directory(('core_'.$subsystem))); 391 } 392 } 393 394 public function test_get_subtype_parent() { 395 global $CFG; 396 397 $this->assertNull(core_component::get_subtype_parent('mod')); 398 399 // Any plugin with more subtypes is ok here. 400 $this->assertFileExists("$CFG->dirroot/mod/assign/db/subplugins.json"); 401 $this->assertSame('mod_assign', core_component::get_subtype_parent('assignsubmission')); 402 $this->assertSame('mod_assign', core_component::get_subtype_parent('assignfeedback')); 403 $this->assertNull(core_component::get_subtype_parent('assignxxxxx')); 404 } 405 406 public function test_get_subplugins() { 407 global $CFG; 408 409 // Any plugin with more subtypes is ok here. 410 $this->assertFileExists("$CFG->dirroot/mod/assign/db/subplugins.json"); 411 412 $subplugins = core_component::get_subplugins('mod_assign'); 413 $this->assertSame(array('assignsubmission', 'assignfeedback'), array_keys($subplugins)); 414 415 $subs = core_component::get_plugin_list('assignsubmission'); 416 $feeds = core_component::get_plugin_list('assignfeedback'); 417 418 $this->assertSame(array_keys($subs), $subplugins['assignsubmission']); 419 $this->assertSame(array_keys($feeds), $subplugins['assignfeedback']); 420 421 // Any plugin without subtypes is ok here. 422 $this->assertFileExists("$CFG->dirroot/mod/choice"); 423 $this->assertFileDoesNotExist("$CFG->dirroot/mod/choice/db/subplugins.json"); 424 425 $this->assertNull(core_component::get_subplugins('mod_choice')); 426 427 $this->assertNull(core_component::get_subplugins('xxxx_yyyy')); 428 } 429 430 public function test_get_plugin_types_with_subplugins() { 431 global $CFG; 432 433 $types = core_component::get_plugin_types_with_subplugins(); 434 435 // Hardcode it here to detect if anybody hacks the code to include more subplugin types. 436 $expected = array( 437 'mod' => "$CFG->dirroot/mod", 438 'editor' => "$CFG->dirroot/lib/editor", 439 'tool' => "$CFG->dirroot/$CFG->admin/tool", 440 'local' => "$CFG->dirroot/local", 441 ); 442 443 $this->assertSame($expected, $types); 444 445 } 446 447 public function test_get_plugin_list_with_file() { 448 $this->resetAfterTest(true); 449 450 // No extra reset here because core_component reset automatically. 451 452 $expected = array(); 453 $reports = core_component::get_plugin_list('report'); 454 foreach ($reports as $name => $fulldir) { 455 if (file_exists("$fulldir/lib.php")) { 456 $expected[] = $name; 457 } 458 } 459 460 // Test cold. 461 $list = core_component::get_plugin_list_with_file('report', 'lib.php', false); 462 $this->assertEquals($expected, array_keys($list)); 463 464 // Test hot. 465 $list = core_component::get_plugin_list_with_file('report', 'lib.php', false); 466 $this->assertEquals($expected, array_keys($list)); 467 468 // Test with include. 469 $list = core_component::get_plugin_list_with_file('report', 'lib.php', true); 470 $this->assertEquals($expected, array_keys($list)); 471 472 // Test missing. 473 $list = core_component::get_plugin_list_with_file('report', 'idontexist.php', true); 474 $this->assertEquals(array(), array_keys($list)); 475 } 476 477 public function test_get_component_classes_in_namespace() { 478 479 // Unexisting. 480 $this->assertCount(0, core_component::get_component_classes_in_namespace('core_unexistingcomponent', 'something')); 481 $this->assertCount(0, core_component::get_component_classes_in_namespace('auth_cas', 'something')); 482 483 // Matches the last namespace level name not partials. 484 $this->assertCount(0, core_component::get_component_classes_in_namespace('auth_cas', 'tas')); 485 $this->assertCount(0, core_component::get_component_classes_in_namespace('core_user', 'course')); 486 $this->assertCount(0, core_component::get_component_classes_in_namespace('mod_forum', 'output\\emaildigest')); 487 $this->assertCount(0, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\emaildigest')); 488 $this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', 'output\\email')); 489 $this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\email')); 490 $this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', 'output\\email\\')); 491 $this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\email\\')); 492 493 // Prefix with backslash if it doesn\'t come prefixed. 494 $this->assertCount(1, core_component::get_component_classes_in_namespace('auth_cas', 'task')); 495 $this->assertCount(1, core_component::get_component_classes_in_namespace('auth_cas', '\\task')); 496 497 // Core as a component works, the function can normalise the component name. 498 $this->assertCount(7, core_component::get_component_classes_in_namespace('core', 'update')); 499 $this->assertCount(7, core_component::get_component_classes_in_namespace('', 'update')); 500 $this->assertCount(7, core_component::get_component_classes_in_namespace('moodle', 'update')); 501 502 // Multiple levels. 503 $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', '\\output\\myprofile\\')); 504 $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', 'output\\myprofile\\')); 505 $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', '\\output\\myprofile')); 506 $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', 'output\\myprofile')); 507 508 // Without namespace it returns classes/ classes. 509 $this->assertCount(5, core_component::get_component_classes_in_namespace('tool_mobile', '')); 510 $this->assertCount(2, core_component::get_component_classes_in_namespace('tool_filetypes')); 511 512 // When no component is specified, classes are returned for the namespace in all components. 513 // (We don't assert exact amounts here as the count of `output` classes will change depending on plugins installed). 514 $this->assertGreaterThan( 515 count(\core_component::get_component_classes_in_namespace('core', 'output')), 516 count(\core_component::get_component_classes_in_namespace(null, 'output'))); 517 518 // Without either a component or namespace it returns an empty array. 519 $this->assertEmpty(\core_component::get_component_classes_in_namespace()); 520 $this->assertEmpty(\core_component::get_component_classes_in_namespace(null)); 521 $this->assertEmpty(\core_component::get_component_classes_in_namespace(null, '')); 522 } 523 524 /** 525 * Data provider for classloader test 526 */ 527 public function classloader_provider() { 528 global $CFG; 529 530 // As part of these tests, we Check that there are no unexpected problems with overlapping PSR namespaces. 531 // This is not in the spec, but may come up in some libraries using both namespaces and PEAR-style class names. 532 // If problems arise we can remove this test, but will need to add a warning. 533 // Normalise to forward slash for testing purposes. 534 $directory = str_replace('\\', '/', $CFG->dirroot) . "/lib/tests/fixtures/component/"; 535 536 $psr0 = [ 537 'psr0' => 'lib/tests/fixtures/component/psr0', 538 'overlap' => 'lib/tests/fixtures/component/overlap' 539 ]; 540 $psr4 = [ 541 'psr4' => 'lib/tests/fixtures/component/psr4', 542 'overlap' => 'lib/tests/fixtures/component/overlap' 543 ]; 544 return [ 545 'PSR-0 Classloading - Root' => [ 546 'psr0' => $psr0, 547 'psr4' => $psr4, 548 'classname' => 'psr0_main', 549 'includedfiles' => "{$directory}psr0/main.php", 550 ], 551 'PSR-0 Classloading - Sub namespace - underscores' => [ 552 'psr0' => $psr0, 553 'psr4' => $psr4, 554 'classname' => 'psr0_subnamespace_example', 555 'includedfiles' => "{$directory}psr0/subnamespace/example.php", 556 ], 557 'PSR-0 Classloading - Sub namespace - slashes' => [ 558 'psr0' => $psr0, 559 'psr4' => $psr4, 560 'classname' => 'psr0\\subnamespace\\slashes', 561 'includedfiles' => "{$directory}psr0/subnamespace/slashes.php", 562 ], 563 'PSR-4 Classloading - Root' => [ 564 'psr0' => $psr0, 565 'psr4' => $psr4, 566 'classname' => 'psr4\\main', 567 'includedfiles' => "{$directory}psr4/main.php", 568 ], 569 'PSR-4 Classloading - Sub namespace' => [ 570 'psr0' => $psr0, 571 'psr4' => $psr4, 572 'classname' => 'psr4\\subnamespace\\example', 573 'includedfiles' => "{$directory}psr4/subnamespace/example.php", 574 ], 575 'PSR-4 Classloading - Ensure underscores are not converted to paths' => [ 576 'psr0' => $psr0, 577 'psr4' => $psr4, 578 'classname' => 'psr4\\subnamespace\\underscore_example', 579 'includedfiles' => "{$directory}psr4/subnamespace/underscore_example.php", 580 ], 581 'Overlap - Ensure no unexpected problems with PSR-4 when overlapping namespaces.' => [ 582 'psr0' => $psr0, 583 'psr4' => $psr4, 584 'classname' => 'overlap\\subnamespace\\example', 585 'includedfiles' => "{$directory}overlap/subnamespace/example.php", 586 ], 587 'Overlap - Ensure no unexpected problems with PSR-0 overlapping namespaces.' => [ 588 'psr0' => $psr0, 589 'psr4' => $psr4, 590 'classname' => 'overlap_subnamespace_example2', 591 'includedfiles' => "{$directory}overlap/subnamespace/example2.php", 592 ], 593 ]; 594 } 595 596 /** 597 * Test the classloader. 598 * 599 * @dataProvider classloader_provider 600 * @param array $psr0 The PSR-0 namespaces to be used in the test. 601 * @param array $psr4 The PSR-4 namespaces to be used in the test. 602 * @param string $classname The name of the class to attempt to load. 603 * @param string $includedfiles The file expected to be loaded. 604 * @runInSeparateProcess 605 */ 606 public function test_classloader($psr0, $psr4, $classname, $includedfiles) { 607 $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces'); 608 $psr0namespaces->setAccessible(true); 609 $psr0namespaces->setValue(null, $psr0); 610 611 $psr4namespaces = new ReflectionProperty('core_component', 'psr4namespaces'); 612 $psr4namespaces->setAccessible(true); 613 $psr4namespaces->setValue(null, $psr4); 614 615 core_component::classloader($classname); 616 if (DIRECTORY_SEPARATOR != '/') { 617 // Denormalise the expected path so that we can quickly compare with get_included_files. 618 $includedfiles = str_replace('/', DIRECTORY_SEPARATOR, $includedfiles); 619 } 620 $this->assertContains($includedfiles, get_included_files()); 621 $this->assertTrue(class_exists($classname, false)); 622 } 623 624 /** 625 * Data provider for psr_classloader test 626 */ 627 public function psr_classloader_provider() { 628 global $CFG; 629 630 // As part of these tests, we Check that there are no unexpected problems with overlapping PSR namespaces. 631 // This is not in the spec, but may come up in some libraries using both namespaces and PEAR-style class names. 632 // If problems arise we can remove this test, but will need to add a warning. 633 // Normalise to forward slash for testing purposes. 634 $dirroot = str_replace('\\', '/', $CFG->dirroot); 635 $directory = "{$dirroot}/lib/tests/fixtures/component/"; 636 637 $psr0 = [ 638 'psr0' => 'lib/tests/fixtures/component/psr0', 639 'overlap' => 'lib/tests/fixtures/component/overlap' 640 ]; 641 $psr4 = [ 642 'psr4' => 'lib/tests/fixtures/component/psr4', 643 'overlap' => 'lib/tests/fixtures/component/overlap' 644 ]; 645 return [ 646 'PSR-0 Classloading - Root' => [ 647 'psr0' => $psr0, 648 'psr4' => $psr4, 649 'classname' => 'psr0_main', 650 'file' => "{$directory}psr0/main.php", 651 ], 652 'PSR-0 Classloading - Sub namespace - underscores' => [ 653 'psr0' => $psr0, 654 'psr4' => $psr4, 655 'classname' => 'psr0_subnamespace_example', 656 'file' => "{$directory}psr0/subnamespace/example.php", 657 ], 658 'PSR-0 Classloading - Sub namespace - slashes' => [ 659 'psr0' => $psr0, 660 'psr4' => $psr4, 661 'classname' => 'psr0\\subnamespace\\slashes', 662 'file' => "{$directory}psr0/subnamespace/slashes.php", 663 ], 664 'PSR-0 Classloading - non-existant file' => [ 665 'psr0' => $psr0, 666 'psr4' => $psr4, 667 'classname' => 'psr0_subnamespace_nonexistant_file', 668 'file' => false, 669 ], 670 'PSR-4 Classloading - Root' => [ 671 'psr0' => $psr0, 672 'psr4' => $psr4, 673 'classname' => 'psr4\\main', 674 'file' => "{$directory}psr4/main.php", 675 ], 676 'PSR-4 Classloading - Sub namespace' => [ 677 'psr0' => $psr0, 678 'psr4' => $psr4, 679 'classname' => 'psr4\\subnamespace\\example', 680 'file' => "{$directory}psr4/subnamespace/example.php", 681 ], 682 'PSR-4 Classloading - Ensure underscores are not converted to paths' => [ 683 'psr0' => $psr0, 684 'psr4' => $psr4, 685 'classname' => 'psr4\\subnamespace\\underscore_example', 686 'file' => "{$directory}psr4/subnamespace/underscore_example.php", 687 ], 688 'PSR-4 Classloading - non-existant file' => [ 689 'psr0' => $psr0, 690 'psr4' => $psr4, 691 'classname' => 'psr4\\subnamespace\\nonexistant', 692 'file' => false, 693 ], 694 'Overlap - Ensure no unexpected problems with PSR-4 when overlapping namespaces.' => [ 695 'psr0' => $psr0, 696 'psr4' => $psr4, 697 'classname' => 'overlap\\subnamespace\\example', 698 'file' => "{$directory}overlap/subnamespace/example.php", 699 ], 700 'Overlap - Ensure no unexpected problems with PSR-0 overlapping namespaces.' => [ 701 'psr0' => $psr0, 702 'psr4' => $psr4, 703 'classname' => 'overlap_subnamespace_example2', 704 'file' => "{$directory}overlap/subnamespace/example2.php", 705 ], 706 'PSR-4 namespaces can come from multiple sources - first source' => [ 707 'psr0' => $psr0, 708 'psr4' => [ 709 'Psr\\Http\\Message' => [ 710 'lib/psr/http-message/src', 711 'lib/psr/http-factory/src', 712 ], 713 ], 714 'classname' => 'Psr\Http\Message\ServerRequestInterface', 715 'includedfiles' => "{$dirroot}/lib/psr/http-message/src/ServerRequestInterface.php", 716 ], 717 'PSR-4 namespaces can come from multiple sources - second source' => [ 718 'psr0' => [], 719 'psr4' => [ 720 'Psr\\Http\\Message' => [ 721 'lib/psr/http-message/src', 722 'lib/psr/http-factory/src', 723 ], 724 ], 725 'classname' => 'Psr\Http\Message\ServerRequestFactoryInterface', 726 'includedfiles' => "{$dirroot}/lib/psr/http-factory/src/ServerRequestFactoryInterface.php", 727 ], 728 ]; 729 } 730 731 /** 732 * Test the PSR classloader. 733 * 734 * @dataProvider psr_classloader_provider 735 * @param array $psr0 The PSR-0 namespaces to be used in the test. 736 * @param array $psr4 The PSR-4 namespaces to be used in the test. 737 * @param string $classname The name of the class to attempt to load. 738 * @param string|bool $file The expected file corresponding to the class or false for nonexistant. 739 * @runInSeparateProcess 740 */ 741 public function test_psr_classloader($psr0, $psr4, $classname, $file) { 742 $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces'); 743 $psr0namespaces->setAccessible(true); 744 $psr0namespaces->setValue(null, $psr0); 745 746 $psr4namespaces = new ReflectionProperty('core_component', 'psr4namespaces'); 747 $psr4namespaces->setAccessible(true); 748 $oldpsr4namespaces = $psr4namespaces->getValue(null); 749 $psr4namespaces->setValue(null, $psr4); 750 751 $component = new ReflectionClass('core_component'); 752 $psrclassloader = $component->getMethod('psr_classloader'); 753 $psrclassloader->setAccessible(true); 754 755 $returnvalue = $psrclassloader->invokeArgs(null, array($classname)); 756 // Normalise to forward slashes for testing comparison. 757 if ($returnvalue) { 758 $returnvalue = str_replace('\\', '/', $returnvalue); 759 } 760 $this->assertEquals($file, $returnvalue); 761 } 762 763 /** 764 * Data provider for get_class_file test 765 */ 766 public function get_class_file_provider() { 767 global $CFG; 768 769 return [ 770 'Getting a file with underscores' => [ 771 'classname' => 'Test_With_Underscores', 772 'prefix' => "Test", 773 'path' => 'test/src', 774 'separators' => ['_'], 775 'result' => $CFG->dirroot . "/test/src/With/Underscores.php", 776 ], 777 'Getting a file with slashes' => [ 778 'classname' => 'Test\\With\\Slashes', 779 'prefix' => "Test", 780 'path' => 'test/src', 781 'separators' => ['\\'], 782 'result' => $CFG->dirroot . "/test/src/With/Slashes.php", 783 ], 784 'Getting a file with multiple namespaces' => [ 785 'classname' => 'Test\\With\\Multiple\\Namespaces', 786 'prefix' => "Test\\With", 787 'path' => 'test/src', 788 'separators' => ['\\'], 789 'result' => $CFG->dirroot . "/test/src/Multiple/Namespaces.php", 790 ], 791 'Getting a file with multiple namespaces (non-existent)' => [ 792 'classname' => 'Nonexistant\\Namespace\\Test', 793 'prefix' => "Test", 794 'path' => 'test/src', 795 'separators' => ['\\'], 796 'result' => false, 797 ], 798 ]; 799 } 800 801 /** 802 * Test the PSR classloader. 803 * 804 * @dataProvider get_class_file_provider 805 * @param string $classname the name of the class. 806 * @param string $prefix The namespace prefix used to identify the base directory of the source files. 807 * @param string $path The relative path to the base directory of the source files. 808 * @param string[] $separators The characters that should be used for separating. 809 * @param string|bool $result The expected result to be returned from get_class_file. 810 */ 811 public function test_get_class_file($classname, $prefix, $path, $separators, $result) { 812 $component = new ReflectionClass('core_component'); 813 $psrclassloader = $component->getMethod('get_class_file'); 814 $psrclassloader->setAccessible(true); 815 816 $file = $psrclassloader->invokeArgs(null, array($classname, $prefix, $path, $separators)); 817 $this->assertEquals($result, $file); 818 } 819 820 /** 821 * Confirm the get_component_list method contains an entry for every component. 822 */ 823 public function test_get_component_list_contains_all_components() { 824 global $CFG; 825 $componentslist = \core_component::get_component_list(); 826 827 // We should have an entry for each plugin type, and one additional for 'core'. 828 $plugintypes = \core_component::get_plugin_types(); 829 $numelementsexpected = count($plugintypes) + 1; 830 $this->assertEquals($numelementsexpected, count($componentslist)); 831 832 // And an entry for each of the plugin types. 833 foreach (array_keys($plugintypes) as $plugintype) { 834 $this->assertArrayHasKey($plugintype, $componentslist); 835 } 836 837 // And finally, one for 'core'. 838 $this->assertArrayHasKey('core', $componentslist); 839 840 // Check a few of the known plugin types to confirm their presence at their respective type index. 841 $this->assertEquals($componentslist['core']['core_comment'], $CFG->dirroot . '/comment'); 842 $this->assertEquals($componentslist['mod']['mod_forum'], $CFG->dirroot . '/mod/forum'); 843 $this->assertEquals($componentslist['tool']['tool_usertours'], $CFG->dirroot . '/' . $CFG->admin . '/tool/usertours'); 844 } 845 846 /** 847 * Test the get_component_names() method. 848 */ 849 public function test_get_component_names() { 850 global $CFG; 851 $componentnames = \core_component::get_component_names(); 852 853 // We should have an entry for each plugin type. 854 $plugintypes = \core_component::get_plugin_types(); 855 $numplugintypes = 0; 856 foreach ($plugintypes as $type => $typedir) { 857 foreach (\core_component::get_plugin_list($type) as $plugin) { 858 $numplugintypes++; 859 } 860 } 861 // And an entry for each core subsystem. 862 $numcomponents = $numplugintypes + count(\core_component::get_core_subsystems()); 863 864 $this->assertEquals($numcomponents, count($componentnames)); 865 866 // Check a few of the known plugin types to confirm their presence at their respective type index. 867 $this->assertContains('core_comment', $componentnames); 868 $this->assertContains('mod_forum', $componentnames); 869 $this->assertContains('tool_usertours', $componentnames); 870 $this->assertContains('core_favourites', $componentnames); 871 } 872 873 /** 874 * Basic tests for APIs related functions in the core_component class. 875 */ 876 public function test_apis_methods() { 877 $apis = core_component::get_core_apis(); 878 $this->assertIsArray($apis); 879 880 $apinames = core_component::get_core_api_names(); 881 $this->assertIsArray($apis); 882 883 // Both should return the very same APIs. 884 $this->assertEquals($apinames, array_keys($apis)); 885 886 $this->assertFalse(core_component::is_core_api('lalala')); 887 $this->assertTrue(core_component::is_core_api('privacy')); 888 } 889 890 /** 891 * Test that the apis.json structure matches expectations 892 * 893 * While we include an apis.schema.json file in core, there isn't any PHP built-in allowing us 894 * to validate it (3rd part libraries needed). Plus the schema doesn't allow to validate things 895 * like uniqueness or sorting. We are going to do all that here. 896 */ 897 public function test_apis_json_validation() { 898 $apis = $sortedapis = core_component::get_core_apis(); 899 ksort($sortedapis); // We'll need this later. 900 901 $subsystems = core_component::get_core_subsystems(); // To verify all apis are pointing to valid subsystems. 902 $subsystems['core'] = 'anything'; // Let's add 'core' because it's a valid component for apis. 903 904 // General structure validations. 905 $this->assertIsArray($apis); 906 $this->assertGreaterThan(25, count($apis)); 907 $this->assertArrayHasKey('privacy', $apis); // Verify a few. 908 $this->assertArrayHasKey('external', $apis); 909 $this->assertArrayHasKey('search', $apis); 910 $this->assertEquals(array_keys($sortedapis), array_keys($apis)); // Verify json is sorted alphabetically. 911 912 // Iterate over all apis and perform more validations. 913 foreach ($apis as $apiname => $attributes) { 914 // Message, to be used later and easier finding the problem. 915 $message = "Validation problem found with API: {$apiname}"; 916 917 $this->assertIsObject($attributes, $message); 918 $this->assertMatchesRegularExpression('/^[a-z][a-z0-9]+$/', $apiname, $message); 919 $this->assertEquals(['component', 'allowedlevel2', 'allowedspread'], array_keys((array)$attributes), $message); 920 921 // Verify attributes. 922 if ($apiname !== 'core') { // Exception for core api, it doesn't have component. 923 // Check that component attribute looks correct. 924 $this->assertMatchesRegularExpression('/^(core|[a-z][a-z0-9_]+)$/', $attributes->component, $message); 925 // Ensure that the api component (without the core_ prefix) is a correct subsystem. 926 $this->assertArrayHasKey(str_replace('core_', '', $attributes->component), $subsystems, $message); 927 } else { 928 $this->assertNull($attributes->component, $message); 929 } 930 931 932 // Now check for the rest of attributes. 933 $this->assertIsBool($attributes->allowedlevel2, $message); 934 $this->assertIsBool($attributes->allowedspread, $message); 935 936 // Cannot spread if level2 is not allowed. 937 $this->assertLessThanOrEqual($attributes->allowedlevel2, $attributes->allowedspread, $message); 938 } 939 } 940 941 /** 942 * Test for monologo icons check in plugins. 943 * 944 * @covers core_component::has_monologo_icon 945 * @return void 946 */ 947 public function test_has_monologo_icon(): void { 948 // The Forum activity plugin has monologo icons. 949 $this->assertTrue(core_component::has_monologo_icon('mod', 'forum')); 950 // The core H5P subsystem doesn't have monologo icons. 951 $this->assertFalse(core_component::has_monologo_icon('core', 'h5p')); 952 // The function will return false for a non-existent component. 953 $this->assertFalse(core_component::has_monologo_icon('randomcomponent', 'h5p')); 954 } 955 956 /* 957 * Tests the getter for the db directory summary hash. 958 * 959 * @covers \core_component::get_all_directory_hashes 960 */ 961 public function test_get_db_directories_hash() { 962 $initial = \core_component::get_all_component_hash(); 963 964 $dir = make_request_directory(); 965 $hashes = \core_component::get_all_directory_hashes([$dir]); 966 $emptydirhash = \core_component::get_all_component_hash([$hashes]); 967 968 // Confirm that a single empty directory is a different hash to the core hash. 969 $this->assertNotEquals($initial, $emptydirhash); 970 971 // Now lets add something to the dir, and check the hash is different. 972 $file = fopen($dir . '/test.php', 'w'); 973 fwrite($file, 'sometestdata'); 974 fclose($file); 975 976 $hashes = \core_component::get_all_directory_hashes([$dir]); 977 $onefiledirhash = \core_component::get_all_component_hash([$hashes]); 978 $this->assertNotEquals($emptydirhash, $onefiledirhash); 979 980 // Now add a subdirectory inside the request dir. This should not affect the hash. 981 mkdir($dir . '/subdir'); 982 $hashes = \core_component::get_all_directory_hashes([$dir]); 983 $finalhash = \core_component::get_all_component_hash([$hashes]); 984 $this->assertEquals($onefiledirhash, $finalhash); 985 } 986 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body