PageRenderTime 104ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 2ms

/course/tests/courselib_test.php

https://bitbucket.org/moodle/moodle
PHP | 7236 lines | 5235 code | 904 blank | 1097 comment | 67 complexity | ed1a88cd9991921cf45ed72a920840df MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
  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. * Course related unit tests
  18. *
  19. * @package core
  20. * @category phpunit
  21. * @copyright 2012 Petr Skoda {@link http://skodak.org}
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. defined('MOODLE_INTERNAL') || die();
  25. global $CFG;
  26. require_once($CFG->dirroot . '/course/lib.php');
  27. require_once($CFG->dirroot . '/course/tests/fixtures/course_capability_assignment.php');
  28. require_once($CFG->dirroot . '/enrol/imsenterprise/tests/imsenterprise_test.php');
  29. class core_course_courselib_testcase extends advanced_testcase {
  30. /**
  31. * Set forum specific test values for calling create_module().
  32. *
  33. * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
  34. */
  35. private function forum_create_set_values(&$moduleinfo) {
  36. // Completion specific to forum - optional.
  37. $moduleinfo->completionposts = 3;
  38. $moduleinfo->completiondiscussions = 1;
  39. $moduleinfo->completionreplies = 2;
  40. // Specific values to the Forum module.
  41. $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
  42. $moduleinfo->type = 'single';
  43. $moduleinfo->trackingtype = FORUM_TRACKING_FORCED;
  44. $moduleinfo->maxbytes = 10240;
  45. $moduleinfo->maxattachments = 2;
  46. // Post threshold for blocking - specific to forum.
  47. $moduleinfo->blockperiod = 60*60*24;
  48. $moduleinfo->blockafter = 10;
  49. $moduleinfo->warnafter = 5;
  50. // Grading of whole forum settings.
  51. $moduleinfo->grade_forum = 0;
  52. }
  53. /**
  54. * Execute test asserts on the saved DB data by create_module($forum).
  55. *
  56. * @param object $moduleinfo - the specific forum values that were used to create a forum.
  57. * @param object $dbmodinstance - the DB values of the created forum.
  58. */
  59. private function forum_create_run_asserts($moduleinfo, $dbmodinstance) {
  60. // Compare values specific to forums.
  61. $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
  62. $this->assertEquals($moduleinfo->type, $dbmodinstance->type);
  63. $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
  64. $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
  65. $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
  66. $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
  67. $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
  68. $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
  69. $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
  70. $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
  71. $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
  72. $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
  73. $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
  74. $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
  75. $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
  76. $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
  77. $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
  78. }
  79. /**
  80. * Set assign module specific test values for calling create_module().
  81. *
  82. * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
  83. */
  84. private function assign_create_set_values(&$moduleinfo) {
  85. // Specific values to the Assign module.
  86. $moduleinfo->alwaysshowdescription = true;
  87. $moduleinfo->submissiondrafts = true;
  88. $moduleinfo->requiresubmissionstatement = true;
  89. $moduleinfo->sendnotifications = true;
  90. $moduleinfo->sendlatenotifications = true;
  91. $moduleinfo->duedate = time() + (7 * 24 * 3600);
  92. $moduleinfo->cutoffdate = time() + (7 * 24 * 3600);
  93. $moduleinfo->gradingduedate = time() + (7 * 24 * 3600);
  94. $moduleinfo->allowsubmissionsfromdate = time();
  95. $moduleinfo->teamsubmission = true;
  96. $moduleinfo->requireallteammemberssubmit = true;
  97. $moduleinfo->teamsubmissiongroupingid = true;
  98. $moduleinfo->blindmarking = true;
  99. $moduleinfo->markingworkflow = true;
  100. $moduleinfo->markingallocation = true;
  101. $moduleinfo->assignsubmission_onlinetext_enabled = true;
  102. $moduleinfo->assignsubmission_file_enabled = true;
  103. $moduleinfo->assignsubmission_file_maxfiles = 1;
  104. $moduleinfo->assignsubmission_file_maxsizebytes = 1000000;
  105. $moduleinfo->assignsubmission_comments_enabled = true;
  106. $moduleinfo->assignfeedback_comments_enabled = true;
  107. $moduleinfo->assignfeedback_offline_enabled = true;
  108. $moduleinfo->assignfeedback_file_enabled = true;
  109. // Advanced grading.
  110. $gradingmethods = grading_manager::available_methods();
  111. $moduleinfo->advancedgradingmethod_submissions = current(array_keys($gradingmethods));
  112. }
  113. /**
  114. * Execute test asserts on the saved DB data by create_module($assign).
  115. *
  116. * @param object $moduleinfo - the specific assign module values that were used to create an assign module.
  117. * @param object $dbmodinstance - the DB values of the created assign module.
  118. */
  119. private function assign_create_run_asserts($moduleinfo, $dbmodinstance) {
  120. global $DB;
  121. $this->assertEquals($moduleinfo->alwaysshowdescription, $dbmodinstance->alwaysshowdescription);
  122. $this->assertEquals($moduleinfo->submissiondrafts, $dbmodinstance->submissiondrafts);
  123. $this->assertEquals($moduleinfo->requiresubmissionstatement, $dbmodinstance->requiresubmissionstatement);
  124. $this->assertEquals($moduleinfo->sendnotifications, $dbmodinstance->sendnotifications);
  125. $this->assertEquals($moduleinfo->duedate, $dbmodinstance->duedate);
  126. $this->assertEquals($moduleinfo->cutoffdate, $dbmodinstance->cutoffdate);
  127. $this->assertEquals($moduleinfo->allowsubmissionsfromdate, $dbmodinstance->allowsubmissionsfromdate);
  128. $this->assertEquals($moduleinfo->teamsubmission, $dbmodinstance->teamsubmission);
  129. $this->assertEquals($moduleinfo->requireallteammemberssubmit, $dbmodinstance->requireallteammemberssubmit);
  130. $this->assertEquals($moduleinfo->teamsubmissiongroupingid, $dbmodinstance->teamsubmissiongroupingid);
  131. $this->assertEquals($moduleinfo->blindmarking, $dbmodinstance->blindmarking);
  132. $this->assertEquals($moduleinfo->markingworkflow, $dbmodinstance->markingworkflow);
  133. $this->assertEquals($moduleinfo->markingallocation, $dbmodinstance->markingallocation);
  134. // The goal not being to fully test assign_add_instance() we'll stop here for the assign tests - to avoid too many DB queries.
  135. // Advanced grading.
  136. $cm = get_coursemodule_from_instance('assign', $dbmodinstance->id);
  137. $contextmodule = context_module::instance($cm->id);
  138. $advancedgradingmethod = $DB->get_record('grading_areas',
  139. array('contextid' => $contextmodule->id,
  140. 'activemethod' => $moduleinfo->advancedgradingmethod_submissions));
  141. $this->assertEquals($moduleinfo->advancedgradingmethod_submissions, $advancedgradingmethod);
  142. }
  143. /**
  144. * Run some asserts test for a specific module for the function create_module().
  145. *
  146. * The function has been created (and is called) for $this->test_create_module().
  147. * Note that the call to MODULE_create_set_values and MODULE_create_run_asserts are done after the common set values/run asserts.
  148. * So if you want, you can overwrite the default values/asserts in the respective functions.
  149. * @param string $modulename Name of the module ('forum', 'assign', 'book'...).
  150. */
  151. private function create_specific_module_test($modulename) {
  152. global $DB, $CFG;
  153. $this->resetAfterTest(true);
  154. $this->setAdminUser();
  155. // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
  156. require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
  157. // Enable avaibility.
  158. // If not enabled all conditional fields will be ignored.
  159. set_config('enableavailability', 1);
  160. // Enable course completion.
  161. // If not enabled all completion settings will be ignored.
  162. set_config('enablecompletion', COMPLETION_ENABLED);
  163. // Enable forum RSS feeds.
  164. set_config('enablerssfeeds', 1);
  165. set_config('forum_enablerssfeeds', 1);
  166. $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
  167. array('createsections'=>true));
  168. $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
  169. // Create assign module instance for test.
  170. $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
  171. $params['course'] = $course->id;
  172. $instance = $generator->create_instance($params);
  173. $assigncm = get_coursemodule_from_instance('assign', $instance->id);
  174. // Module test values.
  175. $moduleinfo = new stdClass();
  176. // Always mandatory generic values to any module.
  177. $moduleinfo->modulename = $modulename;
  178. $moduleinfo->section = 1; // This is the section number in the course. Not the section id in the database.
  179. $moduleinfo->course = $course->id;
  180. $moduleinfo->groupingid = $grouping->id;
  181. $moduleinfo->visible = true;
  182. $moduleinfo->visibleoncoursepage = true;
  183. // Sometimes optional generic values for some modules.
  184. $moduleinfo->name = 'My test module';
  185. $moduleinfo->showdescription = 1; // standard boolean
  186. require_once($CFG->libdir . '/gradelib.php');
  187. $gradecats = grade_get_categories_menu($moduleinfo->course, false);
  188. $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
  189. $moduleinfo->gradecat = $gradecatid;
  190. $moduleinfo->groupmode = VISIBLEGROUPS;
  191. $moduleinfo->cmidnumber = 'idnumber_XXX';
  192. // Completion common to all module.
  193. $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
  194. $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
  195. $moduleinfo->completiongradeitemnumber = 1;
  196. $moduleinfo->completionpassgrade = 0;
  197. $moduleinfo->completionexpected = time() + (7 * 24 * 3600);
  198. // Conditional activity.
  199. $moduleinfo->availability = '{"op":"&","showc":[true,true],"c":[' .
  200. '{"type":"date","d":">=","t":' . time() . '},' .
  201. '{"type":"date","d":"<","t":' . (time() + (7 * 24 * 3600)) . '}' .
  202. ']}';
  203. $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
  204. $moduleinfo->conditiongradegroup = array(array('conditiongradeitemid' => $coursegradeitem->id, 'conditiongrademin' => 10, 'conditiongrademax' => 80));
  205. $moduleinfo->conditionfieldgroup = array(array('conditionfield' => 'email', 'conditionfieldoperator' => \availability_profile\condition::OP_CONTAINS, 'conditionfieldvalue' => '@'));
  206. $moduleinfo->conditioncompletiongroup = array(array('conditionsourcecmid' => $assigncm->id, 'conditionrequiredcompletion' => COMPLETION_COMPLETE)); // "conditionsourcecmid == 0" => none
  207. // Grading and Advanced grading.
  208. require_once($CFG->dirroot . '/rating/lib.php');
  209. $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
  210. $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
  211. $moduleinfo->assesstimestart = time();
  212. $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
  213. // RSS.
  214. $moduleinfo->rsstype = 2;
  215. $moduleinfo->rssarticles = 10;
  216. // Optional intro editor (depends of module).
  217. $draftid_editor = 0;
  218. file_prepare_draft_area($draftid_editor, null, null, null, null);
  219. $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
  220. // Following is the advanced grading method area called 'submissions' for the 'assign' module.
  221. if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
  222. $moduleinfo->grade = 100;
  223. }
  224. // Plagiarism form values.
  225. // No plagiarism plugin installed by default. Use this space to make your own test.
  226. // Values specific to the module.
  227. $modulesetvalues = $modulename.'_create_set_values';
  228. $this->$modulesetvalues($moduleinfo);
  229. // Create the module.
  230. $result = create_module($moduleinfo);
  231. // Retrieve the module info.
  232. $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
  233. $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
  234. // We passed the course section number to create_courses but $dbcm contain the section id.
  235. // We need to retrieve the db course section number.
  236. $section = $DB->get_record('course_sections', array('course' => $dbcm->course, 'id' => $dbcm->section));
  237. // Retrieve the grade item.
  238. $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
  239. 'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
  240. // Compare the values common to all module instances.
  241. $this->assertEquals($moduleinfo->modulename, $dbcm->modname);
  242. $this->assertEquals($moduleinfo->section, $section->section);
  243. $this->assertEquals($moduleinfo->course, $dbcm->course);
  244. $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
  245. $this->assertEquals($moduleinfo->visible, $dbcm->visible);
  246. $this->assertEquals($moduleinfo->completion, $dbcm->completion);
  247. $this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
  248. $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
  249. $this->assertEquals($moduleinfo->completionpassgrade, $dbcm->completionpassgrade);
  250. $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
  251. $this->assertEquals($moduleinfo->availability, $dbcm->availability);
  252. $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
  253. $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
  254. $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
  255. $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
  256. // Optional grade testing.
  257. if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
  258. $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
  259. }
  260. // Some optional (but quite common) to some module.
  261. $this->assertEquals($moduleinfo->name, $dbmodinstance->name);
  262. $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
  263. $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
  264. // Test specific to the module.
  265. $modulerunasserts = $modulename.'_create_run_asserts';
  266. $this->$modulerunasserts($moduleinfo, $dbmodinstance);
  267. return $moduleinfo;
  268. }
  269. /**
  270. * Create module associated blog and tags.
  271. *
  272. * @param object $course Course.
  273. * @param object $modulecontext The context of the module.
  274. */
  275. private function create_module_asscociated_blog($course, $modulecontext) {
  276. global $DB, $CFG;
  277. // Create default group.
  278. $group = new stdClass();
  279. $group->courseid = $course->id;
  280. $group->name = 'Group';
  281. $group->id = $DB->insert_record('groups', $group);
  282. // Create default user.
  283. $user = $this->getDataGenerator()->create_user(array(
  284. 'username' => 'testuser',
  285. 'firstname' => 'Firsname',
  286. 'lastname' => 'Lastname'
  287. ));
  288. // Create default post.
  289. $post = new stdClass();
  290. $post->userid = $user->id;
  291. $post->groupid = $group->id;
  292. $post->content = 'test post content text';
  293. $post->module = 'blog';
  294. $post->id = $DB->insert_record('post', $post);
  295. // Create default tag.
  296. $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id,
  297. 'rawname' => 'Testtagname', 'isstandard' => 1));
  298. // Apply the tag to the blog.
  299. $DB->insert_record('tag_instance', array('tagid' => $tag->id, 'itemtype' => 'user',
  300. 'component' => 'core', 'itemid' => $post->id, 'ordering' => 0));
  301. require_once($CFG->dirroot . '/blog/locallib.php');
  302. $blog = new blog_entry($post->id);
  303. $blog->add_association($modulecontext->id);
  304. return $blog;
  305. }
  306. /**
  307. * Test create_module() for multiple modules defined in the $modules array (first declaration of the function).
  308. */
  309. public function test_create_module() {
  310. // Add the module name you want to test here.
  311. // Create the match MODULENAME_create_set_values() and MODULENAME_create_run_asserts().
  312. $modules = array('forum', 'assign');
  313. // Run all tests.
  314. foreach ($modules as $modulename) {
  315. $this->create_specific_module_test($modulename);
  316. }
  317. }
  318. /**
  319. * Test update_module() for multiple modules defined in the $modules array (first declaration of the function).
  320. */
  321. public function test_update_module() {
  322. // Add the module name you want to test here.
  323. // Create the match MODULENAME_update_set_values() and MODULENAME_update_run_asserts().
  324. $modules = array('forum');
  325. // Run all tests.
  326. foreach ($modules as $modulename) {
  327. $this->update_specific_module_test($modulename);
  328. }
  329. }
  330. /**
  331. * Set forum specific test values for calling update_module().
  332. *
  333. * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
  334. */
  335. private function forum_update_set_values(&$moduleinfo) {
  336. // Completion specific to forum - optional.
  337. $moduleinfo->completionposts = 3;
  338. $moduleinfo->completiondiscussions = 1;
  339. $moduleinfo->completionreplies = 2;
  340. // Specific values to the Forum module.
  341. $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
  342. $moduleinfo->type = 'single';
  343. $moduleinfo->trackingtype = FORUM_TRACKING_FORCED;
  344. $moduleinfo->maxbytes = 10240;
  345. $moduleinfo->maxattachments = 2;
  346. // Post threshold for blocking - specific to forum.
  347. $moduleinfo->blockperiod = 60*60*24;
  348. $moduleinfo->blockafter = 10;
  349. $moduleinfo->warnafter = 5;
  350. // Grading of whole forum settings.
  351. $moduleinfo->grade_forum = 0;
  352. }
  353. /**
  354. * Execute test asserts on the saved DB data by update_module($forum).
  355. *
  356. * @param object $moduleinfo - the specific forum values that were used to update a forum.
  357. * @param object $dbmodinstance - the DB values of the updated forum.
  358. */
  359. private function forum_update_run_asserts($moduleinfo, $dbmodinstance) {
  360. // Compare values specific to forums.
  361. $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
  362. $this->assertEquals($moduleinfo->type, $dbmodinstance->type);
  363. $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
  364. $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
  365. $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
  366. $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
  367. $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
  368. $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
  369. $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
  370. $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
  371. $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
  372. $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
  373. $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
  374. $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
  375. $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
  376. $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
  377. $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
  378. }
  379. /**
  380. * Test a specific type of module.
  381. *
  382. * @param string $modulename - the module name to test
  383. */
  384. private function update_specific_module_test($modulename) {
  385. global $DB, $CFG;
  386. $this->resetAfterTest(true);
  387. $this->setAdminUser();
  388. // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
  389. require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
  390. // Enable avaibility.
  391. // If not enabled all conditional fields will be ignored.
  392. set_config('enableavailability', 1);
  393. // Enable course completion.
  394. // If not enabled all completion settings will be ignored.
  395. set_config('enablecompletion', COMPLETION_ENABLED);
  396. // Enable forum RSS feeds.
  397. set_config('enablerssfeeds', 1);
  398. set_config('forum_enablerssfeeds', 1);
  399. $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
  400. array('createsections'=>true));
  401. $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
  402. // Create assign module instance for testing gradeitem.
  403. $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
  404. $params['course'] = $course->id;
  405. $instance = $generator->create_instance($params);
  406. $assigncm = get_coursemodule_from_instance('assign', $instance->id);
  407. // Create the test forum to update.
  408. $initvalues = new stdClass();
  409. $initvalues->introformat = FORMAT_HTML;
  410. $initvalues->course = $course->id;
  411. $forum = self::getDataGenerator()->create_module('forum', $initvalues);
  412. // Retrieve course module.
  413. $cm = get_coursemodule_from_instance('forum', $forum->id);
  414. // Module test values.
  415. $moduleinfo = new stdClass();
  416. // Always mandatory generic values to any module.
  417. $moduleinfo->coursemodule = $cm->id;
  418. $moduleinfo->modulename = $modulename;
  419. $moduleinfo->course = $course->id;
  420. $moduleinfo->groupingid = $grouping->id;
  421. $moduleinfo->visible = true;
  422. $moduleinfo->visibleoncoursepage = true;
  423. // Sometimes optional generic values for some modules.
  424. $moduleinfo->name = 'My test module';
  425. $moduleinfo->showdescription = 1; // standard boolean
  426. require_once($CFG->libdir . '/gradelib.php');
  427. $gradecats = grade_get_categories_menu($moduleinfo->course, false);
  428. $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
  429. $moduleinfo->gradecat = $gradecatid;
  430. $moduleinfo->groupmode = VISIBLEGROUPS;
  431. $moduleinfo->cmidnumber = 'idnumber_XXX';
  432. // Completion common to all module.
  433. $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
  434. $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
  435. $moduleinfo->completiongradeitemnumber = 1;
  436. $moduleinfo->completionpassgrade = 0;
  437. $moduleinfo->completionexpected = time() + (7 * 24 * 3600);
  438. $moduleinfo->completionunlocked = 1;
  439. // Conditional activity.
  440. $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
  441. $moduleinfo->availability = json_encode(\core_availability\tree::get_root_json(
  442. array(\availability_date\condition::get_json('>=', time()),
  443. \availability_date\condition::get_json('<', time() + (7 * 24 * 3600)),
  444. \availability_grade\condition::get_json($coursegradeitem->id, 10, 80),
  445. \availability_profile\condition::get_json(false, 'email', 'contains', '@'),
  446. \availability_completion\condition::get_json($assigncm->id, COMPLETION_COMPLETE)), '&'));
  447. // Grading and Advanced grading.
  448. require_once($CFG->dirroot . '/rating/lib.php');
  449. $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
  450. $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
  451. $moduleinfo->assesstimestart = time();
  452. $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
  453. // RSS.
  454. $moduleinfo->rsstype = 2;
  455. $moduleinfo->rssarticles = 10;
  456. // Optional intro editor (depends of module).
  457. $draftid_editor = 0;
  458. file_prepare_draft_area($draftid_editor, null, null, null, null);
  459. $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
  460. // Following is the advanced grading method area called 'submissions' for the 'assign' module.
  461. if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
  462. $moduleinfo->grade = 100;
  463. }
  464. // Plagiarism form values.
  465. // No plagiarism plugin installed by default. Use this space to make your own test.
  466. // Values specific to the module.
  467. $modulesetvalues = $modulename.'_update_set_values';
  468. $this->$modulesetvalues($moduleinfo);
  469. // Create the module.
  470. $result = update_module($moduleinfo);
  471. // Retrieve the module info.
  472. $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
  473. $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
  474. // Retrieve the grade item.
  475. $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
  476. 'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
  477. // Compare the values common to all module instances.
  478. $this->assertEquals($moduleinfo->modulename, $dbcm->modname);
  479. $this->assertEquals($moduleinfo->course, $dbcm->course);
  480. $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
  481. $this->assertEquals($moduleinfo->visible, $dbcm->visible);
  482. $this->assertEquals($moduleinfo->completion, $dbcm->completion);
  483. $this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
  484. $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
  485. $this->assertEquals($moduleinfo->completionpassgrade, $dbcm->completionpassgrade);
  486. $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
  487. $this->assertEquals($moduleinfo->availability, $dbcm->availability);
  488. $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
  489. $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
  490. $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
  491. $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
  492. // Optional grade testing.
  493. if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
  494. $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
  495. }
  496. // Some optional (but quite common) to some module.
  497. $this->assertEquals($moduleinfo->name, $dbmodinstance->name);
  498. $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
  499. $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
  500. // Test specific to the module.
  501. $modulerunasserts = $modulename.'_update_run_asserts';
  502. $this->$modulerunasserts($moduleinfo, $dbmodinstance);
  503. return $moduleinfo;
  504. }
  505. /**
  506. * Data provider for course_delete module
  507. *
  508. * @return array An array of arrays contain test data
  509. */
  510. public function provider_course_delete_module() {
  511. $data = array();
  512. $data['assign'] = array('assign', array('duedate' => time()));
  513. $data['quiz'] = array('quiz', array('duedate' => time()));
  514. return $data;
  515. }
  516. /**
  517. * Test the create_course function
  518. */
  519. public function test_create_course() {
  520. global $DB;
  521. $this->resetAfterTest(true);
  522. $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
  523. $course = new stdClass();
  524. $course->fullname = 'Apu loves Unit Təsts';
  525. $course->shortname = 'Spread the lŭve';
  526. $course->idnumber = '123';
  527. $course->summary = 'Awesome!';
  528. $course->summaryformat = FORMAT_PLAIN;
  529. $course->format = 'topics';
  530. $course->newsitems = 0;
  531. $course->category = $defaultcategory;
  532. $original = (array) $course;
  533. $created = create_course($course);
  534. $context = context_course::instance($created->id);
  535. // Compare original and created.
  536. $this->assertEquals($original, array_intersect_key((array) $created, $original));
  537. // Ensure default section is created.
  538. $sectioncreated = $DB->record_exists('course_sections', array('course' => $created->id, 'section' => 0));
  539. $this->assertTrue($sectioncreated);
  540. // Ensure that the shortname isn't duplicated.
  541. try {
  542. $created = create_course($course);
  543. $this->fail('Exception expected');
  544. } catch (moodle_exception $e) {
  545. $this->assertSame(get_string('shortnametaken', 'error', $course->shortname), $e->getMessage());
  546. }
  547. // Ensure that the idnumber isn't duplicated.
  548. $course->shortname .= '1';
  549. try {
  550. $created = create_course($course);
  551. $this->fail('Exception expected');
  552. } catch (moodle_exception $e) {
  553. $this->assertSame(get_string('courseidnumbertaken', 'error', $course->idnumber), $e->getMessage());
  554. }
  555. }
  556. public function test_create_course_with_generator() {
  557. global $DB;
  558. $this->resetAfterTest(true);
  559. $course = $this->getDataGenerator()->create_course();
  560. // Ensure default section is created.
  561. $sectioncreated = $DB->record_exists('course_sections', array('course' => $course->id, 'section' => 0));
  562. $this->assertTrue($sectioncreated);
  563. }
  564. public function test_create_course_sections() {
  565. global $DB;
  566. $this->resetAfterTest(true);
  567. $numsections = 5;
  568. $course = $this->getDataGenerator()->create_course(
  569. array('shortname' => 'GrowingCourse',
  570. 'fullname' => 'Growing Course',
  571. 'numsections' => $numsections),
  572. array('createsections' => true));
  573. // Ensure all 6 (0-5) sections were created and course content cache works properly
  574. $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
  575. $this->assertEquals(range(0, $numsections), $sectionscreated);
  576. // this will do nothing, section already exists
  577. $this->assertFalse(course_create_sections_if_missing($course, $numsections));
  578. // this will create new section
  579. $this->assertTrue(course_create_sections_if_missing($course, $numsections + 1));
  580. // Ensure all 7 (0-6) sections were created and modinfo/sectioninfo cache works properly
  581. $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
  582. $this->assertEquals(range(0, $numsections + 1), $sectionscreated);
  583. }
  584. public function test_update_course() {
  585. global $DB;
  586. $this->resetAfterTest();
  587. $defaultcategory = $DB->get_field_select('course_categories', 'MIN(id)', 'parent = 0');
  588. $course = new stdClass();
  589. $course->fullname = 'Apu loves Unit Təsts';
  590. $course->shortname = 'test1';
  591. $course->idnumber = '1';
  592. $course->summary = 'Awesome!';
  593. $course->summaryformat = FORMAT_PLAIN;
  594. $course->format = 'topics';
  595. $course->newsitems = 0;
  596. $course->numsections = 5;
  597. $course->category = $defaultcategory;
  598. $created = create_course($course);
  599. // Ensure the checks only work on idnumber/shortname that are not already ours.
  600. update_course($created);
  601. $course->shortname = 'test2';
  602. $course->idnumber = '2';
  603. $created2 = create_course($course);
  604. // Test duplicate idnumber.
  605. $created2->idnumber = '1';
  606. try {
  607. update_course($created2);
  608. $this->fail('Expected exception when trying to update a course with duplicate idnumber');
  609. } catch (moodle_exception $e) {
  610. $this->assertEquals(get_string('courseidnumbertaken', 'error', $created2->idnumber), $e->getMessage());
  611. }
  612. // Test duplicate shortname.
  613. $created2->idnumber = '2';
  614. $created2->shortname = 'test1';
  615. try {
  616. update_course($created2);
  617. $this->fail('Expected exception when trying to update a course with a duplicate shortname');
  618. } catch (moodle_exception $e) {
  619. $this->assertEquals(get_string('shortnametaken', 'error', $created2->shortname), $e->getMessage());
  620. }
  621. }
  622. public function test_update_course_section_time_modified() {
  623. global $DB;
  624. $this->resetAfterTest();
  625. // Create the course with sections.
  626. $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
  627. $sections = $DB->get_records('course_sections', array('course' => $course->id));
  628. // Get the last section's time modified value.
  629. $section = array_pop($sections);
  630. $oldtimemodified = $section->timemodified;
  631. // Update the section.
  632. $this->waitForSecond(); // Ensuring that the section update occurs at a different timestamp.
  633. course_update_section($course, $section, array());
  634. // Check that the time has changed.
  635. $section = $DB->get_record('course_sections', array('id' => $section->id));
  636. $newtimemodified = $section->timemodified;
  637. $this->assertGreaterThan($oldtimemodified, $newtimemodified);
  638. }
  639. /**
  640. * Relative dates mode settings provider for course creation.
  641. */
  642. public function create_course_relative_dates_provider() {
  643. return [
  644. [0, 0, 0],
  645. [0, 1, 0],
  646. [1, 0, 0],
  647. [1, 1, 1],
  648. ];
  649. }
  650. /**
  651. * Test create_course by attempting to change the relative dates mode.
  652. *
  653. * @dataProvider create_course_relative_dates_provider
  654. * @param int $setting The value for the 'enablecourserelativedates' admin setting.
  655. * @param int $mode The value for the course's 'relativedatesmode' field.
  656. * @param int $expectedvalue The expected value of the 'relativedatesmode' field after course creation.
  657. */
  658. public function test_relative_dates_mode_for_course_creation($setting, $mode, $expectedvalue) {
  659. global $DB;
  660. $this->resetAfterTest();
  661. set_config('enablecourserelativedates', $setting);
  662. // Generate a course with relative dates mode set to $mode.
  663. $course = $this->getDataGenerator()->create_course(['relativedatesmode' => $mode]);
  664. // Verify that the relative dates match what's expected.
  665. $relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]);
  666. $this->assertEquals($expectedvalue, $relativedatesmode);
  667. }
  668. /**
  669. * Test update_course by attempting to change the relative dates mode.
  670. */
  671. public function test_relative_dates_mode_for_course_update() {
  672. global $DB;
  673. $this->resetAfterTest();
  674. set_config('enablecourserelativedates', 1);
  675. // Generate a course with relative dates mode set to 1.
  676. $course = $this->getDataGenerator()->create_course(['relativedatesmode' => 1]);
  677. // Attempt to update the course with a changed relativedatesmode.
  678. $course->relativedatesmode = 0;
  679. update_course($course);
  680. // Verify that the relative dates mode has not changed.
  681. $relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]);
  682. $this->assertEquals(1, $relativedatesmode);
  683. }
  684. public function test_course_add_cm_to_section() {
  685. global $DB;
  686. $this->resetAfterTest(true);
  687. // Create course with 1 section.
  688. $course = $this->getDataGenerator()->create_course(
  689. array('shortname' => 'GrowingCourse',
  690. 'fullname' => 'Growing Course',
  691. 'numsections' => 1),
  692. array('createsections' => true));
  693. // Trash modinfo.
  694. rebuild_course_cache($course->id, true);
  695. // Create some cms for testing.
  696. $cmids = array();
  697. for ($i=0; $i<4; $i++) {
  698. $cmids[$i] = $DB->insert_record('course_modules', array('course' => $course->id));
  699. }
  700. // Add it to section that exists.
  701. course_add_cm_to_section($course, $cmids[0], 1);
  702. // Check it got added to sequence.
  703. $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
  704. $this->assertEquals($cmids[0], $sequence);
  705. // Add a second, this time using courseid variant of parameters.
  706. $coursecacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
  707. course_add_cm_to_section($course->id, $cmids[1], 1);
  708. $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
  709. $this->assertEquals($cmids[0] . ',' . $cmids[1], $sequence);
  710. // Check that modinfo cache was reset but not rebuilt (important for performance if calling repeatedly).
  711. $this->assertGreaterThan($coursecacherev, $DB->get_field('course', 'cacherev', array('id' => $course->id)));
  712. $this->assertEmpty(cache::make('core', 'coursemodinfo')->get($course->id));
  713. // Add one to section that doesn't exist (this might rebuild modinfo).
  714. course_add_cm_to_section($course, $cmids[2], 2);
  715. $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
  716. $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
  717. $this->assertEquals($cmids[2], $sequence);
  718. // Add using the 'before' option.
  719. course_add_cm_to_section($course, $cmids[3], 2, $cmids[2]);
  720. $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
  721. $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
  722. $this->assertEquals($cmids[3] . ',' . $cmids[2], $sequence);
  723. }
  724. public function test_reorder_sections() {
  725. global $DB;
  726. $this->resetAfterTest(true);
  727. $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
  728. $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
  729. $oldsections = array();
  730. $sections = array();
  731. foreach ($DB->get_records('course_sections', array('course'=>$course->id), 'id') as $section) {
  732. $oldsections[$section->section] = $section->id;
  733. $sections[$section->id] = $section->section;
  734. }
  735. ksort($oldsections);
  736. $neworder = reorder_sections($sections, 2, 4);
  737. $neworder = array_keys($neworder);
  738. $this->assertEquals($oldsections[0], $neworder[0]);
  739. $this->assertEquals($oldsections[1], $neworder[1]);
  740. $this->assertEquals($oldsections[2], $neworder[4]);
  741. $this->assertEquals($oldsections[3], $neworder[2]);
  742. $this->assertEquals($oldsections[4], $neworder[3]);
  743. $this->assertEquals($oldsections[5], $neworder[5]);
  744. $this->assertEquals($oldsections[6], $neworder[6]);
  745. $neworder = reorder_sections($sections, 4, 2);
  746. $neworder = array_keys($neworder);
  747. $this->assertEquals($oldsections[0], $neworder[0]);
  748. $this->assertEquals($oldsections[1], $neworder[1]);
  749. $this->assertEquals($oldsections[2], $neworder[3]);
  750. $this->assertEquals($oldsections[3], $neworder[4]);
  751. $this->assertEquals($oldsections[4], $neworder[2]);
  752. $this->assertEquals($oldsections[5], $neworder[5]);
  753. $this->assertEquals($oldsections[6], $neworder[6]);
  754. $neworder = reorder_sections(1, 2, 4);
  755. $this->assertFalse($neworder);
  756. }
  757. public function test_move_section_down() {
  758. global $DB;
  759. $this->resetAfterTest(true);
  760. $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
  761. $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
  762. $oldsections = array();
  763. foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
  764. $oldsections[$section->section] = $section->id;
  765. }
  766. ksort($oldsections);
  767. // Test move section down..
  768. move_section_to($course, 2, 4);
  769. $sections = array();
  770. foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
  771. $sections[$section->section] = $section->id;
  772. }
  773. ksort($sections);
  774. $this->assertEquals($oldsections[0], $sections[0]);
  775. $this->assertEquals($oldsections[1], $sections[1]);
  776. $this->assertEquals($oldsections[2], $sections[4]);
  777. $this->assertEquals($oldsections[3], $sections[2]);
  778. $this->assertEquals($oldsections[4], $sections[3]);
  779. $this->assertEquals($oldsections[5], $sections[5]);
  780. $this->assertEquals($oldsections[6], $sections[6]);
  781. }
  782. public function test_move_section_up() {
  783. global $DB;
  784. $this->resetAfterTest(true);
  785. $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
  786. $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
  787. $oldsections = array();
  788. foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
  789. $oldsections[$section->section] = $section->id;
  790. }
  791. ksort($oldsections);
  792. // Test move section up..
  793. move_section_to($course, 6, 4);
  794. $sections = array();
  795. foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
  796. $sections[$section->section] = $section->id;
  797. }
  798. ksort($sections);
  799. $this->assertEquals($oldsections[0], $sections[0]);
  800. $this->assertEquals($oldsections[1], $sections[1]);
  801. $this->assertEquals($oldsections[2], $sections[2]);
  802. $this->assertEquals($oldsections[3], $sections[3]);
  803. $this->assertEquals($oldsections[4], $sections[5]);
  804. $this->assertEquals($oldsections[5], $sections[6]);
  805. $this->assertEquals($oldsections[6], $sections[4]);
  806. }
  807. public function test_move_section_marker() {
  808. global $DB;
  809. $this->resetAfterTest(true);
  810. $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
  811. $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
  812. // Set course marker to the section we are going to move..
  813. course_set_marker($course->id, 2);
  814. // Verify that the course marker is set correctly.
  815. $course = $DB->get_record('course', array('id' => $course->id));
  816. $this->assertEquals(2, $course->marker);
  817. // Test move the marked section down..
  818. move_section_to($course, 2, 4);
  819. // Verify that the course marker has been moved along with the section..
  820. $course = $DB->get_record('course', array('id' => $course->id));
  821. $this->assertEquals(4, $course->marker);
  822. // Test move the marked section up..
  823. move_section_to($course, 4, 3);
  824. // Verify that the course marker has been moved along with the section..
  825. $course = $DB->get_record('course', array('id' => $course->id));
  826. $this->assertEquals(3, $course->marker);
  827. // Test moving a non-marked section above the marked section..
  828. move_section_to($course, 4, 2);
  829. // Verify that the course marker has been moved down to accomodate..
  830. $course = $DB->get_record('course', array('id' => $course->id));
  831. $this->assertEquals(4, $course->marker);
  832. // Test moving a non-marked section below the marked section..
  833. move_section_to($course, 3, 6);
  834. // Verify that the course marker has been up to accomodate..
  835. $course = $DB->get_record('course', array('id' => $course->id));
  836. $this->assertEquals(3, $course->marker);
  837. }
  838. public function test_course_can_delete_section() {
  839. global $DB;
  840. $this->resetAfterTest(true);
  841. $generator = $this->getDataGenerator();
  842. $courseweeks = $generator->create_course(
  843. array('numsections' => 5, 'format' => 'weeks'),
  844. array('createsections' => true));
  845. $assign1 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 1));
  846. $assign2 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 2));
  847. $coursetopics = $generator->create_course(
  848. array('numsections' => 5, 'format' => 'topics'),
  849. array('createsections' => true));
  850. $coursesingleactivity = $generator->create_course(
  851. array('format' => 'singleactivity'),
  852. array('createsections' => true));
  853. // Enrol student and teacher.
  854. $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
  855. $student = $generator->create_user();
  856. $teacher = $generator->create_user();
  857. $generator->enrol_user($student->id, $courseweeks->id, $roleids['student']);
  858. $generator->enrol_user($teacher->id, $courseweeks->id, $roleids['editingteacher']);
  859. $generator->enrol_user($student->id, $coursetopics->id, $roleids['student']);
  860. $generator->enrol_user($teacher->id, $coursetopics->id, $roleids['editingteacher']);
  861. $generator->enrol_user($student->id, $coursesingleactivity->id, $roleids['student']);
  862. $generator->enrol_user($teacher->id, $coursesingleactivity->id, $roleids['editingteacher']);
  863. // Teacher should be able to delete sections (except for 0) in topics and weeks format.
  864. $this->setUser($teacher);
  865. // For topics and weeks formats will return false for section 0 and true for any other section.
  866. $this->assertFalse(course_can_delete_section($courseweeks, 0));
  867. $this->assertTrue(course_can_delete_section($courseweeks, 1));
  868. $this->assertFalse(course_can_delete_section($coursetopics, 0));
  869. $this->assertTrue(course_can_delete_section($coursetopics, 1));
  870. // For singleactivity course format no section can be deleted.
  871. $this->assertFalse(course_can_delete_section($coursesingleactivity, 0));
  872. $this->assertFalse(course_can_delete_section($coursesingleactivity, 1));
  873. // Now let's revoke a capability from teacher to manage activity in section 1.
  874. $modulecontext = context_module::instance($assign1->cmid);
  875. assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleids['editingteacher'],
  876. $modulecontext);
  877. $this->assertFalse(course_can_delete_section($courseweeks, 1));
  878. $this->assertTrue(course_can_delete_section($courseweeks, 2));
  879. // Student does not have permissions to delete sections.
  880. $this->setUser($student);
  881. $this->assertFalse(course_can_delete_section($courseweeks, 1));
  882. $this->assertFalse(course_can_delete_section($coursetopics, 1));
  883. $this->assertFalse(course_can_delete_section($coursesingleactivity, 1));
  884. }
  885. public function test_course_delete_section() {
  886. global $DB;
  887. $this->resetAfterTest(true);
  888. $generator = $this->getDataGenerator();
  889. $course = $generator->create_course(array('numsections' => 6, 'format' => 'topics'),
  890. array('createsections' => true));
  891. $assign0 = $generator->create_module('assign', array('course' => $course, 'section' => 0));
  892. $assign1 = $generator->create_module('assign', array('course' => $course, 'section' => 1));
  893. $assign21 = $generator->create_module('assign', array('course' => $course, 'section' => 2));
  894. $assign22 = $generator->create_module('assign', array('course' => $course, 'section' => 2));
  895. $assign3 = $generator->create_module('assign', array('course' => $course, 'section' => 3));
  896. $assign5 = $generator->create_module('assign', array('course' => $course, 'section' => 5));
  897. $assign6 = $generator->create_module('assign', array('course' => $course, 'section' => 6));
  898. $this->setAdminUser();
  899. // Attempt to delete non-existing section.
  900. $this->assertFalse(course_delete_section($course, 10, false));
  901. $this->assertFalse(course_delete_section($course, 9, true));
  902. // Attempt to delete 0-section.
  903. $this->assertFalse(course_delete_section($course, 0, true));
  904. $this->assertTrue($DB->record_exists('course_modules', array('id' => $assign0->cmid)));
  905. // Delete last section.
  906. $this->assertTrue(course_delete_section($course, 6, true));
  907. $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign6->cmid)));
  908. $this->assertEquals(5, course_get_format($course)->get_last_section_number());
  909. // Delete empty section.
  910. $this->assertTrue(course_delete_section($course, 4, false));
  911. $this->assertEquals(4, course_get_format($course)->get_last_section_number());
  912. // Delete section in the middle (2).
  913. $this->assertFalse(course_delete_section($course, 2, false));
  914. $this->assertTrue(course_delete_section($course, 2, true));
  915. $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign21->cmid)));
  916. $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign22->cmid)));
  917. $this->assertEquals(3, course_get_format($course)->get_last_section_number());
  918. $this->assertEquals(array(0 => array($assign0->cmid),
  919. 1 => array($assign1->cmid),
  920. 2 => array($assign3->cmid),
  921. 3 => array($assign5->cmid)), get_fast_modinfo($course)->sections);
  922. // Remove marked section.
  923. course_set_marker($course->id, 1);
  924. $this->assertTrue(course_get_format($course)->is_section_current(1));
  925. $this->assertTrue(course_delete_section($course, 1, true));
  926. $this->assertFalse(course_get_format($course)->is_section_current(1));
  927. }
  928. public function test_get_course_display_name_for_list() {
  929. global $CFG;
  930. $this->resetAfterTest(true);
  931. $course = $this->getDataGenerator()->create_course(array('shortname' => 'FROG101', 'fullname' => 'Introduction to pond life'));
  932. $CFG->courselistshortnames = 0;
  933. $this->assertEquals('Introduction to pond life', get_course_display_name_for_list($course));
  934. $CFG->courselistshortnames = 1;
  935. $this->assertEquals('FROG101 Introduction to pond life', get_course_display_name_for_list($course));
  936. }
  937. public function test_move_module_in_course() {
  938. global $DB;
  939. $this->resetAfterTest(true);
  940. // Setup fixture
  941. $course = $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections' => true));
  942. $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
  943. $cms = get_fast_modinfo($course)->get_cms();
  944. $cm = reset($cms);
  945. $newsection = get_fast_modinfo($course)->get_section_info(3);
  946. $oldsectionid = $cm->section;
  947. // Perform the move
  948. moveto_module($cm, $newsection);
  949. $cms = get_fast_modinfo($course)->get_cms();
  950. $cm = reset($cms);
  951. // Check that the cached modinfo contains the correct section info
  952. $modinfo = get_fast_modinfo($course);
  953. $this->assertTrue(empty($modinfo->sections[0]));
  954. $this->assertFalse(empty($modinfo->sections[3]));
  955. // Check that the old section's sequence no longer contains this ID
  956. $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
  957. $oldsequences = explode(',', $newsection->sequence);
  958. $this->assertFalse(in_array($cm->id, $oldsequences));
  959. // Check that the new section's sequence now contains this ID
  960. $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
  961. $newsequences = explode(',', $newsection->sequence);
  962. $this->assertTrue(in_array($cm->id, $newsequences));
  963. // Check that the section number has been changed in the cm
  964. $this->assertEquals($newsection->id, $cm->section);
  965. // Perform a second move as some issues were only seen on the second move
  966. $newsection = get_fast_modinfo($course)->get_section_info(2);
  967. $oldsectionid = $cm->section;
  968. moveto_module($cm, $newsection);
  969. $cms = get_fast_modinfo($course)->get_cms();
  970. $cm = reset($cms);
  971. // Check that the cached modinfo contains the correct section info
  972. $modinfo = get_fast_modinfo($course);
  973. $this->assertTrue(empty($modinfo->sections[0]));
  974. $this->assertFalse(empty($modinfo->sections[2]));
  975. // Check that the old section's sequence no longer contains this ID
  976. $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
  977. $oldsequences = explode(',', $newsection->sequence);
  978. $this->assertFalse(in_array($cm->id, $oldsequences));
  979. // Check that the new section's sequence now contains this ID
  980. $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
  981. $newsequences = explode(',', $newsection->sequence);
  982. $this->assertTrue(in_array($cm->id, $newsequences));
  983. }
  984. public function test_module_visibility() {
  985. $this->setAdminUser();
  986. $this->resetAfterTest(true);
  987. // Create course and modules.
  988. $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
  989. $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
  990. $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 'course' => $course->id));
  991. $modules = compact('forum', 'assign');
  992. // Hiding the modules.
  993. foreach ($modules as $mod) {
  994. set_coursemodule_visible($mod->cmid, 0);
  995. $this->check_module_visibility($mod, 0, 0);
  996. }
  997. // Showing the modules.
  998. foreach ($modules as $mod) {
  999. set_coursemodule_visible($mod->cmid, 1);
  1000. $this->check_module_visibility($mod, 1, 1);
  1001. }
  1002. }
  1003. public function test_section_visibility_events() {
  1004. $this->setAdminUser();
  1005. $this->resetAfterTest(true);
  1006. $course = $this->getDataGenerator()->create_course(array('numsections' => 1), array('createsections' => true));
  1007. $sectionnumber = 1;
  1008. $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
  1009. array('section' => $sectionnumber));
  1010. $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
  1011. 'course' => $course->id), array('section' => $sectionnumber));
  1012. $sink = $this->redirectEvents();
  1013. set_section_visible($course->id, $sectionnumber, 0);
  1014. $events = $sink->get_events();
  1015. // Extract the number of events related to what we are testing, other events
  1016. // such as course_section_updated could have been triggered.
  1017. $count = 0;
  1018. foreach ($events as $event) {
  1019. if ($event instanceof \core\event\course_module_updated) {
  1020. $count++;
  1021. }
  1022. }
  1023. $this->assertSame(2, $count);
  1024. $sink->close();
  1025. }
  1026. public function test_section_visibility() {
  1027. $this->setAdminUser();
  1028. $this->resetAfterTest(true);
  1029. // Create course.
  1030. $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
  1031. $sink = $this->redirectEvents();
  1032. // Testing an empty section.
  1033. $sectionnumber = 1;
  1034. set_section_visible($course->id, $sectionnumber, 0);
  1035. $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
  1036. $this->assertEquals($section_info->visible, 0);
  1037. set_section_visible($course->id, $sectionnumber, 1);
  1038. $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
  1039. $this->assertEquals($section_info->visible, 1);
  1040. // Checking that an event was fired.
  1041. $events = $sink->get_events();
  1042. $this->assertInstanceOf('\core\event\course_section_updated', $events[0]);
  1043. $sink->close();
  1044. // Testing a section with visible modules.
  1045. $sectionnumber = 2;
  1046. $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
  1047. array('section' => $sectionnumber));
  1048. $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
  1049. 'course' => $course->id), array('section' => $sectionnumber));
  1050. $modules = compact('forum', 'assign');
  1051. set_section_visible($course->id, $sectionnumber, 0);
  1052. $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
  1053. $this->assertEquals($section_info->visible, 0);
  1054. foreach ($modules as $mod) {
  1055. $this->check_module_visibility($mod, 0, 1);
  1056. }
  1057. set_section_visible($course->id, $sectionnumber, 1);
  1058. $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
  1059. $this->assertEquals($section_info->visible, 1);
  1060. foreach ($modules as $mod) {
  1061. $this->check_module_visibility($mod, 1, 1);
  1062. }
  1063. // Testing a section with hidden modules, which should stay hidden.
  1064. $sectionnumber = 3;
  1065. $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
  1066. array('section' => $sectionnumber));
  1067. $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
  1068. 'course' => $course->id), array('section' => $sectionnumber));
  1069. $modules = compact('forum', 'assign');
  1070. foreach ($modules as $mod) {
  1071. set_coursemodule_visible($mod->cmid, 0);
  1072. $this->check_module_visibility($mod, 0, 0);
  1073. }
  1074. set_section_visible($course->id, $sectionnumber, 0);
  1075. $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
  1076. $this->assertEquals($section_info->visible, 0);
  1077. foreach ($modules as $mod) {
  1078. $this->check_module_visibility($mod, 0, 0);
  1079. }
  1080. set_section_visible($course->id, $sectionnumber, 1);
  1081. $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
  1082. $this->assertEquals($section_info->visible, 1);
  1083. foreach ($modules as $mod) {
  1084. $this->check_module_visibility($mod, 0, 0);
  1085. }
  1086. }
  1087. /**
  1088. * Helper function to assert that a module has correctly been made visible, or hidden.
  1089. *
  1090. * @param stdClass $mod module information
  1091. * @param int $visibility the current state of the module
  1092. * @param int $visibleold the current state of the visibleold property
  1093. * @return void
  1094. */
  1095. public function check_module_visibility($mod, $visibility, $visibleold) {
  1096. global $DB;
  1097. $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid);
  1098. $this->assertEquals($visibility, $cm->visible);
  1099. $this->assertEquals($visibleold, $cm->visibleold);
  1100. // Check the module grade items.
  1101. $grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $cm->modname,
  1102. 'iteminstance' => $cm->instance, 'courseid' => $cm->course));
  1103. if ($grade_items) {
  1104. foreach ($grade_items as $grade_item) {
  1105. if ($visibility) {
  1106. $this->assertFalse($grade_item->is_hidden(), "$cm->modname grade_item not visible");
  1107. } else {
  1108. $this->assertTrue($grade_item->is_hidden(), "$cm->modname grade_item not hidden");
  1109. }
  1110. }
  1111. }
  1112. // Check the events visibility.
  1113. if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $cm->modname))) {
  1114. foreach ($events as $event) {
  1115. $calevent = new calendar_event($event);
  1116. $this->assertEquals($visibility, $calevent->visible, "$cm->modname calendar_event visibility");
  1117. }
  1118. }
  1119. }
  1120. public function test_course_page_type_list() {
  1121. global $DB;
  1122. $this->resetAfterTest(true);
  1123. // Create a category.
  1124. $category = new stdClass();
  1125. $category->name = 'Test Category';
  1126. $testcategory = $this->getDataGenerator()->create_category($category);
  1127. // Create a course.
  1128. $course = new stdClass();
  1129. $course->fullname = 'Apu loves Unit Təsts';
  1130. $course->shortname = 'Spread the lŭve';
  1131. $course->idnumber = '123';
  1132. $course->summary = 'Awesome!';
  1133. $course->summaryformat = FORMAT_PLAIN;
  1134. $course->format = 'topics';
  1135. $course->newsitems = 0;
  1136. $course->numsections = 5;
  1137. $course->category = $testcategory->id;
  1138. $testcourse = $this->getDataGenerator()->create_course($course);
  1139. // Create contexts.
  1140. $coursecontext = context_course::instance($testcourse->id);
  1141. $parentcontext = $coursecontext->get_parent_context(); // Not actually used.
  1142. $pagetype = 'page-course-x'; // Not used either.
  1143. $pagetypelist = course_page_type_list($pagetype, $parentcontext, $coursecontext);
  1144. // Page type lists for normal courses.
  1145. $testpagetypelist1 = array();
  1146. $testpagetypelist1['*'] = 'Any page';
  1147. $testpagetypelist1['course-*'] = 'Any course page';
  1148. $testpagetypelist1['course-view-*'] = 'Any type of course main page';
  1149. $this->assertEquals($testpagetypelist1, $pagetypelist);
  1150. // Get the context for the front page course.
  1151. $sitecoursecontext = context_course::instance(SITEID);
  1152. $pagetypelist = course_page_type_list($pagetype, $parentcontext, $sitecoursecontext);
  1153. // Page type list for the front page course.
  1154. $testpagetypelist2 = array('*' => 'Any page');
  1155. $this->assertEquals($testpagetypelist2, $pagetypelist);
  1156. // Make sure that providing no current context to the function doesn't result in an error.
  1157. // Calls made from generate_page_type_patterns() may provide null values.
  1158. $pagetypelist = course_page_type_list($pagetype, null, null);
  1159. $this->assertEquals($pagetypelist, $testpagetypelist1);
  1160. }
  1161. public function test_compare_activities_by_time_desc() {
  1162. // Let's create some test data.
  1163. $activitiesivities = array();
  1164. $x = new stdClass();
  1165. $x->timestamp = null;
  1166. $activities[] = $x;
  1167. $x = new stdClass();
  1168. $x->timestamp = 1;
  1169. $activities[] = $x;
  1170. $x = new stdClass();
  1171. $x->timestamp = 3;
  1172. $activities[] = $x;
  1173. $x = new stdClass();
  1174. $x->timestamp = 0;
  1175. $activities[] = $x;
  1176. $x = new stdClass();
  1177. $x->timestamp = 5;
  1178. $activities[] = $x;
  1179. $x = new stdClass();
  1180. $activities[] = $x;
  1181. $x = new stdClass();
  1182. $x->timestamp = 5;
  1183. $activities[] = $x;
  1184. // Do the sorting.
  1185. usort($activities, 'compare_activities_by_time_desc');
  1186. // Let's check the result.
  1187. $last = 10;
  1188. foreach($activities as $activity) {
  1189. if (empty($activity->timestamp)) {
  1190. $activity->timestamp = 0;
  1191. }
  1192. $this->assertLessThanOrEqual($last, $activity->timestamp);
  1193. }
  1194. }
  1195. public function test_compare_activities_by_time_asc() {
  1196. // Let's create some test data.
  1197. $activities = array();
  1198. $x = new stdClass();
  1199. $x->timestamp = null;
  1200. $activities[] = $x;
  1201. $x = new stdClass();
  1202. $x->timestamp = 1;
  1203. $activities[] = $x;
  1204. $x = new stdClass();
  1205. $x->timestamp = 3;
  1206. $activities[] = $x;
  1207. $x = new stdClass();
  1208. $x->timestamp = 0;
  1209. $activities[] = $x;
  1210. $x = new stdClass();
  1211. $x->timestamp = 5;
  1212. $activities[] = $x;
  1213. $x = new stdClass();
  1214. $activities[] = $x;
  1215. $x = new stdClass();
  1216. $x->timestamp = 5;
  1217. $activities[] = $x;
  1218. // Do the sorting.
  1219. usort($activities, 'compare_activities_by_time_asc');
  1220. // Let's check the result.
  1221. $last = 0;
  1222. foreach($activities as $activity) {
  1223. if (empty($activity->timestamp)) {
  1224. $activity->timestamp = 0;
  1225. }
  1226. $this->assertGreaterThanOrEqual($last, $activity->timestamp);
  1227. }
  1228. }
  1229. /**
  1230. * Tests moving a module between hidden/visible sections and
  1231. * verifies that the course/module visiblity seettings are
  1232. * retained.
  1233. */
  1234. public function test_moveto_module_between_hidden_sections() {
  1235. global $DB;
  1236. $this->resetAfterTest(true);
  1237. $course = $this->getDataGenerator()->create_course(array('numsections' => 4), array('createsections' => true));
  1238. $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
  1239. $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
  1240. $quiz= $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
  1241. // Set the page as hidden
  1242. set_coursemodule_visible($page->cmid, 0);
  1243. // Set sections 3 as hidden.
  1244. set_section_visible($course->id, 3, 0);
  1245. $modinfo = get_fast_modinfo($course);
  1246. $hiddensection = $modinfo->get_section_info(3);
  1247. // New section is definitely not visible:
  1248. $this->assertEquals($hiddensection->visible, 0);
  1249. $forumcm = $modinfo->cms[$forum->cmid];
  1250. $pagecm = $modinfo->cms[$page->cmid];
  1251. // Move the forum and the page to a hidden section, make sure moveto_module returns 0 as new visibility state.
  1252. $this->assertEquals(0, moveto_module($forumcm, $hiddensection));
  1253. $this->assertEquals(0, moveto_module($pagecm, $hiddensection));
  1254. $modinfo = get_fast_modinfo($course);
  1255. // Verify that forum and page have been moved to the hidden section and quiz has not.
  1256. $this->assertContainsEquals($forum->cmid, $modinfo->sections[3]);
  1257. $this->assertContainsEquals($page->cmid, $modinfo->sections[3]);
  1258. $this->assertNotContainsEquals($quiz->cmid, $modinfo->sections[3]);
  1259. // Verify that forum has been made invisible.
  1260. $forumcm = $modinfo->cms[$forum->cmid];
  1261. $this->assertEquals($forumcm->visible, 0);
  1262. // Verify that old state has been retained.
  1263. $this->assertEquals($forumcm->visibleold, 1);
  1264. // Verify that page has stayed invisible.
  1265. $pagecm = $modinfo->cms[$page->cmid];
  1266. $this->assertEquals($pagecm->visible, 0);
  1267. // Verify that old state has been retained.
  1268. $this->assertEquals($pagecm->visibleold, 0);
  1269. // Verify that quiz has been unaffected.
  1270. $quizcm = $modinfo->cms[$quiz->cmid];
  1271. $this->assertEquals($quizcm->visible, 1);
  1272. // Move forum and page back to visible section.
  1273. // Make sure the visibility is restored to the original value (visible for forum and hidden for page).
  1274. $visiblesection = $modinfo->get_section_info(2);
  1275. $this->assertEquals(1, moveto_module($forumcm, $visiblesection));
  1276. $this->assertEquals(0, moveto_module($pagecm, $visiblesection));
  1277. $modinfo = get_fast_modinfo($course);
  1278. // Double check that forum has been made visible.
  1279. $forumcm = $modinfo->cms[$forum->cmid];
  1280. $this->assertEquals($forumcm->visible, 1);
  1281. // Double check that page has stayed invisible.
  1282. $pagecm = $modinfo->cms[$page->cmid];
  1283. $this->assertEquals($pagecm->visible, 0);
  1284. // Move the page in the same section (this is what mod duplicate does).
  1285. // Visibility of page remains 0.
  1286. $this->assertEquals(0, moveto_module($pagecm, $visiblesection, $forumcm));
  1287. // Double check that the the page is still hidden.
  1288. $modinfo = get_fast_modinfo($course);
  1289. $pagecm = $modinfo->cms[$page->cmid];
  1290. $this->assertEquals($pagecm->visible, 0);
  1291. }
  1292. /**
  1293. * Tests moving a module around in the same section. moveto_module()
  1294. * is called this way in modduplicate.
  1295. */
  1296. public function test_moveto_module_in_same_section() {
  1297. global $DB;
  1298. $this->resetAfterTest(true);
  1299. $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
  1300. $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
  1301. $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
  1302. // Simulate inconsistent visible/visibleold values (MDL-38713).
  1303. $cm = $DB->get_record('course_modules', array('id' => $page->cmid), '*', MUST_EXIST);
  1304. $cm->visible = 0;
  1305. $cm->visibleold = 1;
  1306. $DB->update_record('course_modules', $cm);
  1307. $modinfo = get_fast_modinfo($course);
  1308. $forumcm = $modinfo->cms[$forum->cmid];
  1309. $pagecm = $modinfo->cms[$page->cmid];
  1310. // Verify that page is hidden.
  1311. $this->assertEquals($pagecm->visible, 0);
  1312. // Verify section 0 is where all mods added.
  1313. $section = $modinfo->get_section_info(0);
  1314. $this->assertEquals($section->id, $forumcm->section);
  1315. $this->assertEquals($section->id, $pagecm->section);
  1316. // Move the page inside the hidden section. Make sure it is hidden.
  1317. $this->assertEquals(0, moveto_module($pagecm, $section, $forumcm));
  1318. // Double check that the the page is still hidden.
  1319. $modinfo = get_fast_modinfo($course);
  1320. $pagecm = $modinfo->cms[$page->cmid];
  1321. $this->assertEquals($pagecm->visible, 0);
  1322. }
  1323. /**
  1324. * Tests the function that deletes a course module
  1325. *
  1326. * @param string $type The type of module for the test
  1327. * @param array $options The options for the module creation
  1328. * @dataProvider provider_course_delete_module
  1329. */
  1330. public function test_course_delete_module($type, $options) {
  1331. global $DB;
  1332. $this->resetAfterTest(true);
  1333. $this->setAdminUser();
  1334. // Create course and modules.
  1335. $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
  1336. $options['course'] = $course->id;
  1337. // Generate an assignment with due date (will generate a course event).
  1338. $module = $this->getDataGenerator()->create_module($type, $options);
  1339. // Get the module context.
  1340. $modcontext = context_module::instance($module->cmid);
  1341. $assocblog = $this->create_module_asscociated_blog($course, $modcontext);
  1342. // Verify context exists.
  1343. $this->assertInstanceOf('context_module', $modcontext);
  1344. // Make module specific messes.
  1345. switch ($type) {
  1346. case 'assign':
  1347. // Add some tags to this assignment.
  1348. core_tag_tag::set_item_tags('mod_assign', 'assign', $module->id, $modcontext, array('Tag 1', 'Tag 2', 'Tag 3'));
  1349. core_tag_tag::set_item_tags('core', 'course_modules', $module->cmid, $modcontext, array('Tag 3', 'Tag 4', 'Tag 5'));
  1350. // Confirm the tag instances were added.
  1351. $criteria = array('component' => 'mod_assign', 'itemtype' => 'assign', 'contextid' => $modcontext->id);
  1352. $this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
  1353. $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id);
  1354. $this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
  1355. // Verify event assignment event has been generated.
  1356. $eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type));
  1357. $this->assertEquals(1, $eventcount);
  1358. break;
  1359. case 'quiz':
  1360. $qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
  1361. $qcat = $qgen->create_question_category(array('contextid' => $modcontext->id));
  1362. $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
  1363. $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
  1364. break;
  1365. default:
  1366. break;
  1367. }
  1368. // Run delete..
  1369. course_delete_module($module->cmid);
  1370. // Verify the context has been removed.
  1371. $this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING));
  1372. // Verify the course_module record has been deleted.
  1373. $cmcount = $DB->count_records('course_modules', array('id' => $module->cmid));
  1374. $this->assertEmpty($cmcount);
  1375. // Verify the blog_association record has been deleted.
  1376. $this->assertCount(0, $DB->get_records('blog_association',
  1377. array('contextid' => $modcontext->id)));
  1378. // Verify the blog post record has been deleted.
  1379. $this->assertCount(0, $DB->get_records('post',
  1380. array('id' => $assocblog->id)));
  1381. // Verify the tag instance record has been deleted.
  1382. $this->assertCount(0, $DB->get_records('tag_instance',
  1383. array('itemid' => $assocblog->id)));
  1384. // Test clean up of module specific messes.
  1385. switch ($type) {
  1386. case 'assign':
  1387. // Verify event assignment events have been removed.
  1388. $eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type));
  1389. $this->assertEmpty($eventcount);
  1390. // Verify the tag instances were deleted.
  1391. $criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id);
  1392. $this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
  1393. $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id);
  1394. $this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
  1395. break;
  1396. case 'quiz':
  1397. // Verify category deleted.
  1398. $criteria = array('contextid' => $modcontext->id);
  1399. $this->assertEquals(0, $DB->count_records('question_categories', $criteria));
  1400. // Verify questions deleted.
  1401. $criteria = array('category' => $qcat->id);
  1402. $this->assertEquals(0, $DB->count_records('question', $criteria));
  1403. break;
  1404. default:
  1405. break;
  1406. }
  1407. }
  1408. /**
  1409. * Test that triggering a course_created event works as expected.
  1410. */
  1411. public function test_course_created_event() {
  1412. global $DB;
  1413. $this->resetAfterTest();
  1414. // Catch the events.
  1415. $sink = $this->redirectEvents();
  1416. // Create the course with an id number which is used later when generating a course via the imsenterprise plugin.
  1417. $data = new stdClass();
  1418. $data->idnumber = 'idnumber';
  1419. $course = $this->getDataGenerator()->create_course($data);
  1420. // Get course from DB for comparison.
  1421. $course = $DB->get_record('course', array('id' => $course->id));
  1422. // Capture the event.
  1423. $events = $sink->get_events();
  1424. $sink->close();
  1425. // Validate the event.
  1426. $event = $events[0];
  1427. $this->assertInstanceOf('\core\event\course_created', $event);
  1428. $this->assertEquals('course', $event->objecttable);
  1429. $this->assertEquals($course->id, $event->objectid);
  1430. $this->assertEquals(context_course::instance($course->id), $event->get_context());
  1431. $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
  1432. $this->assertEquals('course_created', $event->get_legacy_eventname());
  1433. $this->assertEventLegacyData($course, $event);
  1434. $expectedlog = array(SITEID, 'course', 'new', 'view.php?id=' . $course->id, $course->fullname . ' (ID ' . $course->id . ')');
  1435. $this->assertEventLegacyLogData($expectedlog, $event);
  1436. // Now we want to trigger creating a course via the imsenterprise.
  1437. // Delete the course we created earlier, as we want the imsenterprise plugin to create this.
  1438. // We do not want print out any of the text this function generates while doing this, which is why
  1439. // we are using ob_start() and ob_end_clean().
  1440. ob_start();
  1441. delete_course($course);
  1442. ob_end_clean();
  1443. // Create the XML file we want to use.
  1444. $course->category = (array)$course->category;
  1445. $imstestcase = new enrol_imsenterprise_testcase();
  1446. $imstestcase->imsplugin = enrol_get_plugin('imsenterprise');
  1447. $imstestcase->set_test_config();
  1448. $imstestcase->set_xml_file(false, array($course));
  1449. // Capture the event.
  1450. $sink = $this->redirectEvents();
  1451. $imstestcase->imsplugin->cron();
  1452. $events = $sink->get_events();
  1453. $sink->close();
  1454. $event = null;
  1455. foreach ($events as $eventinfo) {
  1456. if ($eventinfo instanceof \core\event\course_created ) {
  1457. $event = $eventinfo;
  1458. break;
  1459. }
  1460. }
  1461. // Validate the event triggered is \core\event\course_created. There is no need to validate the other values
  1462. // as they have already been validated in the previous steps. Here we only want to make sure that when the
  1463. // imsenterprise plugin creates a course an event is triggered.
  1464. $this->assertInstanceOf('\core\event\course_created', $event);
  1465. $this->assertEventContextNotUsed($event);
  1466. }
  1467. /**
  1468. * Test that triggering a course_updated event works as expected.
  1469. */
  1470. public function test_course_updated_event() {
  1471. global $DB;
  1472. $this->resetAfterTest();
  1473. // Create a course.
  1474. $course = $this->getDataGenerator()->create_course();
  1475. // Create a category we are going to move this course to.
  1476. $category = $this->getDataGenerator()->create_category();
  1477. // Create a hidden category we are going to move this course to.
  1478. $categoryhidden = $this->getDataGenerator()->create_category(array('visible' => 0));
  1479. // Update course and catch course_updated event.
  1480. $sink = $this->redirectEvents();
  1481. update_course($course);
  1482. $events = $sink->get_events();
  1483. $sink->close();
  1484. // Get updated course information from the DB.
  1485. $updatedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
  1486. // Validate event.
  1487. $event = array_shift($events);
  1488. $this->assertInstanceOf('\core\event\course_updated', $event);
  1489. $this->assertEquals('course', $event->objecttable);
  1490. $this->assertEquals($updatedcourse->id, $event->objectid);
  1491. $this->assertEquals(context_course::instance($course->id), $event->get_context());
  1492. $url = new moodle_url('/course/edit.php', array('id' => $event->objectid));
  1493. $this->assertEquals($url, $event->get_url());
  1494. $this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $event->objectid));
  1495. $this->assertEquals('course_updated', $event->get_legacy_eventname());
  1496. $this->assertEventLegacyData($updatedcourse, $event);
  1497. $expectedlog = array($updatedcourse->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id);
  1498. $this->assertEventLegacyLogData($expectedlog, $event);
  1499. // Move course and catch course_updated event.
  1500. $sink = $this->redirectEvents();
  1501. move_courses(array($course->id), $category->id);
  1502. $events = $sink->get_events();
  1503. $sink->close();
  1504. // Return the moved course information from the DB.
  1505. $movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
  1506. // Validate event.
  1507. $event = array_shift($events);
  1508. $this->assertInstanceOf('\core\event\course_updated', $event);
  1509. $this->assertEquals('course', $event->objecttable);
  1510. $this->assertEquals($movedcourse->id, $event->objectid);
  1511. $this->assertEquals(context_course::instance($course->id), $event->get_context());
  1512. $this->assertEquals($movedcourse, $event->get_record_snapshot('course', $movedcourse->id));
  1513. $this->assertEquals('course_updated', $event->get_legacy_eventname());
  1514. $this->assertEventLegacyData($movedcourse, $event);
  1515. $expectedlog = array($movedcourse->id, 'course', 'move', 'edit.php?id=' . $movedcourse->id, $movedcourse->id);
  1516. $this->assertEventLegacyLogData($expectedlog, $event);
  1517. // Move course to hidden category and catch course_updated event.
  1518. $sink = $this->redirectEvents();
  1519. move_courses(array($course->id), $categoryhidden->id);
  1520. $events = $sink->get_events();
  1521. $sink->close();
  1522. // Return the moved course information from the DB.
  1523. $movedcoursehidden = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
  1524. // Validate event.
  1525. $event = array_shift($events);
  1526. $this->assertInstanceOf('\core\event\course_updated', $event);
  1527. $this->assertEquals('course', $event->objecttable);
  1528. $this->assertEquals($movedcoursehidden->id, $event->objectid);
  1529. $this->assertEquals(context_course::instance($course->id), $event->get_context());
  1530. $this->assertEquals($movedcoursehidden, $event->get_record_snapshot('course', $movedcoursehidden->id));
  1531. $this->assertEquals('course_updated', $event->get_legacy_eventname());
  1532. $this->assertEventLegacyData($movedcoursehidden, $event);
  1533. $expectedlog = array($movedcoursehidden->id, 'course', 'move', 'edit.php?id=' . $movedcoursehidden->id, $movedcoursehidden->id);
  1534. $this->assertEventLegacyLogData($expectedlog, $event);
  1535. $this->assertEventContextNotUsed($event);
  1536. }
  1537. /**
  1538. * Test that triggering a course_updated event logs changes.
  1539. */
  1540. public function test_course_updated_event_with_changes() {
  1541. global $DB;
  1542. $this->resetAfterTest();
  1543. // Create a course.
  1544. $course = $this->getDataGenerator()->create_course((object)['visible' => 1]);
  1545. $editedcourse = $DB->get_record('course', ['id' => $course->id]);
  1546. $editedcourse->visible = 0;
  1547. // Update course and catch course_updated event.
  1548. $sink = $this->redirectEvents();
  1549. update_course($editedcourse);
  1550. $events = $sink->get_events();
  1551. $sink->close();
  1552. $event = array_shift($events);
  1553. $this->assertInstanceOf('\core\event\course_updated', $event);
  1554. $otherdata = [
  1555. 'shortname' => $course->shortname,
  1556. 'fullname' => $course->fullname,
  1557. 'updatedfields' => [
  1558. 'visible' => 0
  1559. ]
  1560. ];
  1561. $this->assertEquals($otherdata, $event->other);
  1562. }
  1563. /**
  1564. * Test that triggering a course_deleted event works as expected.
  1565. */
  1566. public function test_course_deleted_event() {
  1567. $this->resetAfterTest();
  1568. // Create the course.
  1569. $course = $this->getDataGenerator()->create_course();
  1570. // Save the course context before we delete the course.
  1571. $coursecontext = context_course::instance($course->id);
  1572. // Catch the update event.
  1573. $sink = $this->redirectEvents();
  1574. // Call delete_course() which will trigger the course_deleted event and the course_content_deleted
  1575. // event. This function prints out data to the screen, which we do not want during a PHPUnit test,
  1576. // so use ob_start and ob_end_clean to prevent this.
  1577. ob_start();
  1578. delete_course($course);
  1579. ob_end_clean();
  1580. // Capture the event.
  1581. $events = $sink->get_events();
  1582. $sink->close();
  1583. // Validate the event.
  1584. $event = array_pop($events);
  1585. $this->assertInstanceOf('\core\event\course_deleted', $event);
  1586. $this->assertEquals('course', $event->objecttable);
  1587. $this->assertEquals($course->id, $event->objectid);
  1588. $this->assertEquals($coursecontext->id, $event->contextid);
  1589. $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
  1590. $this->assertEquals('course_deleted', $event->get_legacy_eventname());
  1591. $eventdata = $event->get_data();
  1592. $this->assertSame($course->idnumber, $eventdata['other']['idnumber']);
  1593. $this->assertSame($course->fullname, $eventdata['other']['fullname']);
  1594. $this->assertSame($course->shortname, $eventdata['other']['shortname']);
  1595. // The legacy data also passed the context in the course object and substitutes timemodified with the current date.
  1596. $expectedlegacy = clone($course);
  1597. $expectedlegacy->context = $coursecontext;
  1598. $expectedlegacy->timemodified = $event->timecreated;
  1599. $this->assertEventLegacyData($expectedlegacy, $event);
  1600. // Validate legacy log data.
  1601. $expectedlog = array(SITEID, 'course', 'delete', 'view.php?id=' . $course->id, $course->fullname . '(ID ' . $course->id . ')');
  1602. $this->assertEventLegacyLogData($expectedlog, $event);
  1603. $this->assertEventContextNotUsed($event);
  1604. }
  1605. /**
  1606. * Test that triggering a course_content_deleted event works as expected.
  1607. */
  1608. public function test_course_content_deleted_event() {
  1609. global $DB;
  1610. $this->resetAfterTest();
  1611. // Create the course.
  1612. $course = $this->getDataGenerator()->create_course();
  1613. // Get the course from the DB. The data generator adds some extra properties, such as
  1614. // numsections, to the course object which will fail the assertions later on.
  1615. $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
  1616. // Save the course context before we delete the course.
  1617. $coursecontext = context_course::instance($course->id);
  1618. // Catch the update event.
  1619. $sink = $this->redirectEvents();
  1620. remove_course_contents($course->id, false);
  1621. // Capture the event.
  1622. $events = $sink->get_events();
  1623. $sink->close();
  1624. // Validate the event.
  1625. $event = array_pop($events);
  1626. $this->assertInstanceOf('\core\event\course_content_deleted', $event);
  1627. $this->assertEquals('course', $event->objecttable);
  1628. $this->assertEquals($course->id, $event->objectid);
  1629. $this->assertEquals($coursecontext->id, $event->contextid);
  1630. $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
  1631. $this->assertEquals('course_content_removed', $event->get_legacy_eventname());
  1632. // The legacy data also passed the context and options in the course object.
  1633. $course->context = $coursecontext;
  1634. $course->options = array();
  1635. $this->assertEventLegacyData($course, $event);
  1636. $this->assertEventContextNotUsed($event);
  1637. }
  1638. /**
  1639. * Test that triggering a course_category_deleted event works as expected.
  1640. */
  1641. public function test_course_category_deleted_event() {
  1642. $this->resetAfterTest();
  1643. // Create a category.
  1644. $category = $this->getDataGenerator()->create_category();
  1645. // Save the original record/context before it is deleted.
  1646. $categoryrecord = $category->get_db_record();
  1647. $categorycontext = context_coursecat::instance($category->id);
  1648. // Catch the update event.
  1649. $sink = $this->redirectEvents();
  1650. // Delete the category.
  1651. $category->delete_full();
  1652. // Capture the event.
  1653. $events = $sink->get_events();
  1654. $sink->close();
  1655. // Validate the event.
  1656. $event = $events[0];
  1657. $this->assertInstanceOf('\core\event\course_category_deleted', $event);
  1658. $this->assertEquals('course_categories', $event->objecttable);
  1659. $this->assertEquals($category->id, $event->objectid);
  1660. $this->assertEquals($categorycontext->id, $event->contextid);
  1661. $this->assertEquals([
  1662. 'name' => $category->name,
  1663. ], $event->other);
  1664. $this->assertEquals($categoryrecord, $event->get_record_snapshot($event->objecttable, $event->objectid));
  1665. $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
  1666. $this->assertEquals(null, $event->get_url());
  1667. $this->assertEventLegacyData($category, $event);
  1668. $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category->name . '(ID ' . $category->id . ')');
  1669. $this->assertEventLegacyLogData($expectedlog, $event);
  1670. // Create two categories.
  1671. $category = $this->getDataGenerator()->create_category();
  1672. $category2 = $this->getDataGenerator()->create_category();
  1673. // Save the original record/context before it is moved and then deleted.
  1674. $category2record = $category2->get_db_record();
  1675. $category2context = context_coursecat::instance($category2->id);
  1676. // Catch the update event.
  1677. $sink = $this->redirectEvents();
  1678. // Move the category.
  1679. $category2->delete_move($category->id);
  1680. // Capture the event.
  1681. $events = $sink->get_events();
  1682. $sink->close();
  1683. // Validate the event.
  1684. $event = $events[0];
  1685. $this->assertInstanceOf('\core\event\course_category_deleted', $event);
  1686. $this->assertEquals('course_categories', $event->objecttable);
  1687. $this->assertEquals($category2->id, $event->objectid);
  1688. $this->assertEquals($category2context->id, $event->contextid);
  1689. $this->assertEquals([
  1690. 'name' => $category2->name,
  1691. 'contentmovedcategoryid' => $category->id,
  1692. ], $event->other);
  1693. $this->assertEquals($category2record, $event->get_record_snapshot($event->objecttable, $event->objectid));
  1694. $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
  1695. $this->assertEventLegacyData($category2, $event);
  1696. $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category2->name . '(ID ' . $category2->id . ')');
  1697. $this->assertEventLegacyLogData($expectedlog, $event);
  1698. $this->assertEventContextNotUsed($event);
  1699. }
  1700. /**
  1701. * Test that triggering a course_backup_created event works as expected.
  1702. */
  1703. public function test_course_backup_created_event() {
  1704. global $CFG;
  1705. // Get the necessary files to perform backup and restore.
  1706. require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
  1707. require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
  1708. $this->resetAfterTest();
  1709. // Set to admin user.
  1710. $this->setAdminUser();
  1711. // The user id is going to be 2 since we are the admin user.
  1712. $userid = 2;
  1713. // Create a course.
  1714. $course = $this->getDataGenerator()->create_course();
  1715. // Create backup file and save it to the backup location.
  1716. $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
  1717. backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
  1718. $sink = $this->redirectEvents();
  1719. $bc->execute_plan();
  1720. // Capture the event.
  1721. $events = $sink->get_events();
  1722. $sink->close();
  1723. // Validate the event.
  1724. $event = array_pop($events);
  1725. $this->assertInstanceOf('\core\event\course_backup_created', $event);
  1726. $this->assertEquals('course', $event->objecttable);
  1727. $this->assertEquals($bc->get_courseid(), $event->objectid);
  1728. $this->assertEquals(context_course::instance($bc->get_courseid())->id, $event->contextid);
  1729. $url = new moodle_url('/course/view.php', array('id' => $event->objectid));
  1730. $this->assertEquals($url, $event->get_url());
  1731. $this->assertEventContextNotUsed($event);
  1732. // Destroy the resource controller since we are done using it.
  1733. $bc->destroy();
  1734. }
  1735. /**
  1736. * Test that triggering a course_restored event works as expected.
  1737. */
  1738. public function test_course_restored_event() {
  1739. global $CFG;
  1740. // Get the necessary files to perform backup and restore.
  1741. require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
  1742. require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
  1743. $this->resetAfterTest();
  1744. // Set to admin user.
  1745. $this->setAdminUser();
  1746. // The user id is going to be 2 since we are the admin user.
  1747. $userid = 2;
  1748. // Create a course.
  1749. $course = $this->getDataGenerator()->create_course();
  1750. // Create backup file and save it to the backup location.
  1751. $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
  1752. backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
  1753. $bc->execute_plan();
  1754. $results = $bc->get_results();
  1755. $file = $results['backup_destination'];
  1756. $fp = get_file_packer('application/vnd.moodle.backup');
  1757. $filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event';
  1758. $file->extract_to_pathname($fp, $filepath);
  1759. $bc->destroy();
  1760. // Now we want to catch the restore course event.
  1761. $sink = $this->redirectEvents();
  1762. // Now restore the course to trigger the event.
  1763. $rc = new restore_controller('test-restore-course-event', $course->id, backup::INTERACTIVE_NO,
  1764. backup::MODE_GENERAL, $userid, backup::TARGET_NEW_COURSE);
  1765. $rc->execute_precheck();
  1766. $rc->execute_plan();
  1767. // Capture the event.
  1768. $events = $sink->get_events();
  1769. $sink->close();
  1770. // Validate the event.
  1771. $event = array_pop($events);
  1772. $this->assertInstanceOf('\core\event\course_restored', $event);
  1773. $this->assertEquals('course', $event->objecttable);
  1774. $this->assertEquals($rc->get_courseid(), $event->objectid);
  1775. $this->assertEquals(context_course::instance($rc->get_courseid())->id, $event->contextid);
  1776. $this->assertEquals('course_restored', $event->get_legacy_eventname());
  1777. $legacydata = (object) array(
  1778. 'courseid' => $rc->get_courseid(),
  1779. 'userid' => $rc->get_userid(),
  1780. 'type' => $rc->get_type(),
  1781. 'target' => $rc->get_target(),
  1782. 'mode' => $rc->get_mode(),
  1783. 'operation' => $rc->get_operation(),
  1784. 'samesite' => $rc->is_samesite()
  1785. );
  1786. $url = new moodle_url('/course/view.php', array('id' => $event->objectid));
  1787. $this->assertEquals($url, $event->get_url());
  1788. $this->assertEventLegacyData($legacydata, $event);
  1789. $this->assertEventContextNotUsed($event);
  1790. // Destroy the resource controller since we are done using it.
  1791. $rc->destroy();
  1792. }
  1793. /**
  1794. * Test that triggering a course_section_updated event works as expected.
  1795. */
  1796. public function test_course_section_updated_event() {
  1797. global $DB;
  1798. $this->resetAfterTest();
  1799. // Create the course with sections.
  1800. $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
  1801. $sections = $DB->get_records('course_sections', array('course' => $course->id));
  1802. $coursecontext = context_course::instance($course->id);
  1803. $section = array_pop($sections);
  1804. $section->name = 'Test section';
  1805. $section->summary = 'Test section summary';
  1806. $DB->update_record('course_sections', $section);
  1807. // Trigger an event for course section update.
  1808. $event = \core\event\course_section_updated::create(
  1809. array(
  1810. 'objectid' => $section->id,
  1811. 'courseid' => $course->id,
  1812. 'context' => context_course::instance($course->id),
  1813. 'other' => array(
  1814. 'sectionnum' => $section->section
  1815. )
  1816. )
  1817. );
  1818. $event->add_record_snapshot('course_sections', $section);
  1819. // Trigger and catch event.
  1820. $sink = $this->redirectEvents();
  1821. $event->trigger();
  1822. $events = $sink->get_events();
  1823. $sink->close();
  1824. // Validate the event.
  1825. $event = $events[0];
  1826. $this->assertInstanceOf('\core\event\course_section_updated', $event);
  1827. $this->assertEquals('course_sections', $event->objecttable);
  1828. $this->assertEquals($section->id, $event->objectid);
  1829. $this->assertEquals($course->id, $event->courseid);
  1830. $this->assertEquals($coursecontext->id, $event->contextid);
  1831. $this->assertEquals($section->section, $event->other['sectionnum']);
  1832. $expecteddesc = "The user with id '{$event->userid}' updated section number '{$event->other['sectionnum']}' for the course with id '{$event->courseid}'";
  1833. $this->assertEquals($expecteddesc, $event->get_description());
  1834. $url = new moodle_url('/course/editsection.php', array('id' => $event->objectid));
  1835. $this->assertEquals($url, $event->get_url());
  1836. $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
  1837. $id = $section->id;
  1838. $sectionnum = $section->section;
  1839. $expectedlegacydata = array($course->id, "course", "editsection", 'editsection.php?id=' . $id, $sectionnum);
  1840. $this->assertEventLegacyLogData($expectedlegacydata, $event);
  1841. $this->assertEventContextNotUsed($event);
  1842. }
  1843. /**
  1844. * Test that triggering a course_section_deleted event works as expected.
  1845. */
  1846. public function test_course_section_deleted_event() {
  1847. global $USER, $DB;
  1848. $this->resetAfterTest();
  1849. $sink = $this->redirectEvents();
  1850. // Create the course with sections.
  1851. $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
  1852. $sections = $DB->get_records('course_sections', array('course' => $course->id), 'section');
  1853. $coursecontext = context_course::instance($course->id);
  1854. $section = array_pop($sections);
  1855. course_delete_section($course, $section);
  1856. $events = $sink->get_events();
  1857. $event = array_pop($events); // Delete section event.
  1858. $sink->close();
  1859. // Validate event data.
  1860. $this->assertInstanceOf('\core\event\course_section_deleted', $event);
  1861. $this->assertEquals('course_sections', $event->objecttable);
  1862. $this->assertEquals($section->id, $event->objectid);
  1863. $this->assertEquals($course->id, $event->courseid);
  1864. $this->assertEquals($coursecontext->id, $event->contextid);
  1865. $this->assertEquals($section->section, $event->other['sectionnum']);
  1866. $expecteddesc = "The user with id '{$event->userid}' deleted section number '{$event->other['sectionnum']}' " .
  1867. "(section name '{$event->other['sectionname']}') for the course with id '{$event->courseid}'";
  1868. $this->assertEquals($expecteddesc, $event->get_description());
  1869. $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
  1870. $this->assertNull($event->get_url());
  1871. // Test legacy data.
  1872. $sectionnum = $section->section;
  1873. $expectedlegacydata = array($course->id, "course", "delete section", 'view.php?id=' . $course->id, $sectionnum);
  1874. $this->assertEventLegacyLogData($expectedlegacydata, $event);
  1875. $this->assertEventContextNotUsed($event);
  1876. }
  1877. public function test_course_integrity_check() {
  1878. global $DB;
  1879. $this->resetAfterTest(true);
  1880. $course = $this->getDataGenerator()->create_course(array('numsections' => 1),
  1881. array('createsections'=>true));
  1882. $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
  1883. array('section' => 0));
  1884. $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id),
  1885. array('section' => 0));
  1886. $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id),
  1887. array('section' => 0));
  1888. $correctseq = join(',', array($forum->cmid, $page->cmid, $quiz->cmid));
  1889. $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
  1890. $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
  1891. $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
  1892. $this->assertEquals($correctseq, $section0->sequence);
  1893. $this->assertEmpty($section1->sequence);
  1894. $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
  1895. $this->assertEquals($section0->id, $cms[$page->cmid]->section);
  1896. $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
  1897. $this->assertEmpty(course_integrity_check($course->id));
  1898. // Now let's make manual change in DB and let course_integrity_check() fix it:
  1899. // 1. Module appears twice in one section.
  1900. $DB->update_record('course_sections', array('id' => $section0->id, 'sequence' => $section0->sequence. ','. $page->cmid));
  1901. $this->assertEquals(
  1902. array('Failed integrity check for course ['. $course->id.
  1903. ']. Sequence for course section ['. $section0->id. '] is "'.
  1904. $section0->sequence. ','. $page->cmid. '", must be "'.
  1905. $section0->sequence. '"'),
  1906. course_integrity_check($course->id));
  1907. $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
  1908. $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
  1909. $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
  1910. $this->assertEquals($correctseq, $section0->sequence);
  1911. $this->assertEmpty($section1->sequence);
  1912. $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
  1913. $this->assertEquals($section0->id, $cms[$page->cmid]->section);
  1914. $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
  1915. // 2. Module appears in two sections (last section wins).
  1916. $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''. $page->cmid));
  1917. // First message about double mentioning in sequence, second message about wrong section field for $page.
  1918. $this->assertEquals(array(
  1919. 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
  1920. '] must be removed from sequence of section ['. $section0->id.
  1921. '] because it is also present in sequence of section ['. $section1->id. ']',
  1922. 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
  1923. '] points to section ['. $section0->id. '] instead of ['. $section1->id. ']'),
  1924. course_integrity_check($course->id));
  1925. $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
  1926. $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
  1927. $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
  1928. $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
  1929. $this->assertEquals(''. $page->cmid, $section1->sequence);
  1930. $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
  1931. $this->assertEquals($section1->id, $cms[$page->cmid]->section);
  1932. $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
  1933. // 3. Module id is not present in course_section.sequence (integrity check with $fullcheck = false).
  1934. $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
  1935. $this->assertEmpty(course_integrity_check($course->id)); // Not an error!
  1936. $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
  1937. $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
  1938. $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
  1939. $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
  1940. $this->assertEmpty($section1->sequence);
  1941. $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
  1942. $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
  1943. $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
  1944. // 4. Module id is not present in course_section.sequence (integrity check with $fullcheck = true).
  1945. $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
  1946. $page->cmid. '] is missing from sequence of section ['. $section1->id. ']'),
  1947. course_integrity_check($course->id, null, null, true)); // Error!
  1948. $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
  1949. $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
  1950. $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
  1951. $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
  1952. $this->assertEquals(''. $page->cmid, $section1->sequence); // Yay, module added to section.
  1953. $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
  1954. $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
  1955. $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
  1956. // 5. Module id is not present in course_section.sequence and it's section is invalid (integrity check with $fullcheck = true).
  1957. $DB->update_record('course_modules', array('id' => $page->cmid, 'section' => 8765));
  1958. $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
  1959. $this->assertEquals(array(
  1960. 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
  1961. '] is missing from sequence of section ['. $section0->id. ']',
  1962. 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
  1963. '] points to section [8765] instead of ['. $section0->id. ']'),
  1964. course_integrity_check($course->id, null, null, true));
  1965. $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
  1966. $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
  1967. $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
  1968. $this->assertEquals($forum->cmid. ','. $quiz->cmid. ','. $page->cmid, $section0->sequence); // Module added to section.
  1969. $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
  1970. $this->assertEquals($section0->id, $cms[$page->cmid]->section); // Section changed to section0.
  1971. $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
  1972. // 6. Module is deleted from course_modules but not deleted in sequence (integrity check with $fullcheck = true).
  1973. $DB->delete_records('course_modules', array('id' => $page->cmid));
  1974. $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
  1975. $page->cmid. '] does not exist but is present in the sequence of section ['. $section0->id. ']'),
  1976. course_integrity_check($course->id, null, null, true));
  1977. $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
  1978. $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
  1979. $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
  1980. $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
  1981. $this->assertEmpty($section1->sequence);
  1982. $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
  1983. $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
  1984. $this->assertEquals(2, count($cms));
  1985. }
  1986. /**
  1987. * Tests for event related to course module creation.
  1988. */
  1989. public function test_course_module_created_event() {
  1990. global $USER;
  1991. $this->resetAfterTest();
  1992. $this->setAdminUser();
  1993. // Create an assign module.
  1994. $sink = $this->redirectEvents();
  1995. $course = $this->getDataGenerator()->create_course();
  1996. $module = $this->getDataGenerator()->create_module('assign', ['course' => $course]);
  1997. $events = $sink->get_events();
  1998. $eventscount = 0;
  1999. // Validate event data.
  2000. foreach ($events as $event) {
  2001. if ($event instanceof \core\event\course_module_created) {
  2002. $eventscount++;
  2003. $this->assertEquals($module->cmid, $event->objectid);
  2004. $this->assertEquals($USER->id, $event->userid);
  2005. $this->assertEquals('course_modules', $event->objecttable);
  2006. $url = new moodle_url('/mod/assign/view.php', array('id' => $module->cmid));
  2007. $this->assertEquals($url, $event->get_url());
  2008. // Test legacy data.
  2009. $this->assertSame('mod_created', $event->get_legacy_eventname());
  2010. $eventdata = new stdClass();
  2011. $eventdata->modulename = 'assign';
  2012. $eventdata->name = $module->name;
  2013. $eventdata->cmid = $module->cmid;
  2014. $eventdata->courseid = $module->course;
  2015. $eventdata->userid = $USER->id;
  2016. $this->assertEventLegacyData($eventdata, $event);
  2017. $arr = array(
  2018. array($module->course, "course", "add mod", "../mod/assign/view.php?id=$module->cmid", "assign $module->id"),
  2019. array($module->course, "assign", "add", "view.php?id=$module->cmid", $module->id, $module->cmid)
  2020. );
  2021. $this->assertEventLegacyLogData($arr, $event);
  2022. $this->assertEventContextNotUsed($event);
  2023. }
  2024. }
  2025. // Only one \core\event\course_module_created event should be triggered.
  2026. $this->assertEquals(1, $eventscount);
  2027. // Let us see if duplicating an activity results in a nice course module created event.
  2028. $sink->clear();
  2029. $course = get_course($module->course);
  2030. $cm = get_coursemodule_from_id('assign', $module->cmid, 0, false, MUST_EXIST);
  2031. $newcm = duplicate_module($course, $cm);
  2032. $events = $sink->get_events();
  2033. $eventscount = 0;
  2034. $sink->close();
  2035. foreach ($events as $event) {
  2036. if ($event instanceof \core\event\course_module_created) {
  2037. $eventscount++;
  2038. // Validate event data.
  2039. $this->assertInstanceOf('\core\event\course_module_created', $event);
  2040. $this->assertEquals($newcm->id, $event->objectid);
  2041. $this->assertEquals($USER->id, $event->userid);
  2042. $this->assertEquals($course->id, $event->courseid);
  2043. $url = new moodle_url('/mod/assign/view.php', array('id' => $newcm->id));
  2044. $this->assertEquals($url, $event->get_url());
  2045. }
  2046. }
  2047. // Only one \core\event\course_module_created event should be triggered.
  2048. $this->assertEquals(1, $eventscount);
  2049. }
  2050. /**
  2051. * Tests for event validations related to course module creation.
  2052. */
  2053. public function test_course_module_created_event_exceptions() {
  2054. $this->resetAfterTest();
  2055. // Generate data.
  2056. $modinfo = $this->create_specific_module_test('assign');
  2057. $context = context_module::instance($modinfo->coursemodule);
  2058. // Test not setting instanceid.
  2059. try {
  2060. $event = \core\event\course_module_created::create(array(
  2061. 'courseid' => $modinfo->course,
  2062. 'context' => $context,
  2063. 'objectid' => $modinfo->coursemodule,
  2064. 'other' => array(
  2065. 'modulename' => 'assign',
  2066. 'name' => 'My assignment',
  2067. )
  2068. ));
  2069. $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
  2070. other['instanceid']");
  2071. } catch (coding_exception $e) {
  2072. $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
  2073. }
  2074. // Test not setting modulename.
  2075. try {
  2076. $event = \core\event\course_module_created::create(array(
  2077. 'courseid' => $modinfo->course,
  2078. 'context' => $context,
  2079. 'objectid' => $modinfo->coursemodule,
  2080. 'other' => array(
  2081. 'instanceid' => $modinfo->instance,
  2082. 'name' => 'My assignment',
  2083. )
  2084. ));
  2085. $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
  2086. other['modulename']");
  2087. } catch (coding_exception $e) {
  2088. $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
  2089. }
  2090. // Test not setting name.
  2091. try {
  2092. $event = \core\event\course_module_created::create(array(
  2093. 'courseid' => $modinfo->course,
  2094. 'context' => $context,
  2095. 'objectid' => $modinfo->coursemodule,
  2096. 'other' => array(
  2097. 'modulename' => 'assign',
  2098. 'instanceid' => $modinfo->instance,
  2099. )
  2100. ));
  2101. $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
  2102. other['name']");
  2103. } catch (coding_exception $e) {
  2104. $this->assertStringContainsString("The 'name' value must be set in other.", $e->getMessage());
  2105. }
  2106. }
  2107. /**
  2108. * Tests for event related to course module updates.
  2109. */
  2110. public function test_course_module_updated_event() {
  2111. global $USER, $DB;
  2112. $this->resetAfterTest();
  2113. // Update a forum module.
  2114. $sink = $this->redirectEvents();
  2115. $modinfo = $this->update_specific_module_test('forum');
  2116. $events = $sink->get_events();
  2117. $eventscount = 0;
  2118. $sink->close();
  2119. $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
  2120. $mod = $DB->get_record('forum', array('id' => $cm->instance), '*', MUST_EXIST);
  2121. // Validate event data.
  2122. foreach ($events as $event) {
  2123. if ($event instanceof \core\event\course_module_updated) {
  2124. $eventscount++;
  2125. $this->assertEquals($cm->id, $event->objectid);
  2126. $this->assertEquals($USER->id, $event->userid);
  2127. $this->assertEquals('course_modules', $event->objecttable);
  2128. $url = new moodle_url('/mod/forum/view.php', array('id' => $cm->id));
  2129. $this->assertEquals($url, $event->get_url());
  2130. // Test legacy data.
  2131. $this->assertSame('mod_updated', $event->get_legacy_eventname());
  2132. $eventdata = new stdClass();
  2133. $eventdata->modulename = 'forum';
  2134. $eventdata->name = $mod->name;
  2135. $eventdata->cmid = $cm->id;
  2136. $eventdata->courseid = $cm->course;
  2137. $eventdata->userid = $USER->id;
  2138. $this->assertEventLegacyData($eventdata, $event);
  2139. $arr = array(
  2140. array($cm->course, "course", "update mod", "../mod/forum/view.php?id=$cm->id", "forum $cm->instance"),
  2141. array($cm->course, "forum", "update", "view.php?id=$cm->id", $cm->instance, $cm->id)
  2142. );
  2143. $this->assertEventLegacyLogData($arr, $event);
  2144. $this->assertEventContextNotUsed($event);
  2145. }
  2146. }
  2147. // Only one \core\event\course_module_updated event should be triggered.
  2148. $this->assertEquals(1, $eventscount);
  2149. }
  2150. /**
  2151. * Tests for create_from_cm method.
  2152. */
  2153. public function test_course_module_create_from_cm() {
  2154. $this->resetAfterTest();
  2155. $this->setAdminUser();
  2156. // Create course and modules.
  2157. $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
  2158. // Generate an assignment.
  2159. $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
  2160. // Get the module context.
  2161. $modcontext = context_module::instance($assign->cmid);
  2162. // Get course module.
  2163. $cm = get_coursemodule_from_id(null, $assign->cmid, $course->id, false, MUST_EXIST);
  2164. // Create an event from course module.
  2165. $event = \core\event\course_module_updated::create_from_cm($cm, $modcontext);
  2166. // Trigger the events.
  2167. $sink = $this->redirectEvents();
  2168. $event->trigger();
  2169. $events = $sink->get_events();
  2170. $event2 = array_pop($events);
  2171. // Test event data.
  2172. $this->assertInstanceOf('\core\event\course_module_updated', $event);
  2173. $this->assertEquals($cm->id, $event2->objectid);
  2174. $this->assertEquals($modcontext, $event2->get_context());
  2175. $this->assertEquals($cm->modname, $event2->other['modulename']);
  2176. $this->assertEquals($cm->instance, $event2->other['instanceid']);
  2177. $this->assertEquals($cm->name, $event2->other['name']);
  2178. $this->assertEventContextNotUsed($event2);
  2179. $this->assertSame('mod_updated', $event2->get_legacy_eventname());
  2180. $arr = array(
  2181. array($cm->course, "course", "update mod", "../mod/assign/view.php?id=$cm->id", "assign $cm->instance"),
  2182. array($cm->course, "assign", "update", "view.php?id=$cm->id", $cm->instance, $cm->id)
  2183. );
  2184. $this->assertEventLegacyLogData($arr, $event);
  2185. }
  2186. /**
  2187. * Tests for event validations related to course module update.
  2188. */
  2189. public function test_course_module_updated_event_exceptions() {
  2190. $this->resetAfterTest();
  2191. // Generate data.
  2192. $modinfo = $this->create_specific_module_test('assign');
  2193. $context = context_module::instance($modinfo->coursemodule);
  2194. // Test not setting instanceid.
  2195. try {
  2196. $event = \core\event\course_module_updated::create(array(
  2197. 'courseid' => $modinfo->course,
  2198. 'context' => $context,
  2199. 'objectid' => $modinfo->coursemodule,
  2200. 'other' => array(
  2201. 'modulename' => 'assign',
  2202. 'name' => 'My assignment',
  2203. )
  2204. ));
  2205. $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
  2206. other['instanceid']");
  2207. } catch (coding_exception $e) {
  2208. $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
  2209. }
  2210. // Test not setting modulename.
  2211. try {
  2212. $event = \core\event\course_module_updated::create(array(
  2213. 'courseid' => $modinfo->course,
  2214. 'context' => $context,
  2215. 'objectid' => $modinfo->coursemodule,
  2216. 'other' => array(
  2217. 'instanceid' => $modinfo->instance,
  2218. 'name' => 'My assignment',
  2219. )
  2220. ));
  2221. $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
  2222. other['modulename']");
  2223. } catch (coding_exception $e) {
  2224. $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
  2225. }
  2226. // Test not setting name.
  2227. try {
  2228. $event = \core\event\course_module_updated::create(array(
  2229. 'courseid' => $modinfo->course,
  2230. 'context' => $context,
  2231. 'objectid' => $modinfo->coursemodule,
  2232. 'other' => array(
  2233. 'modulename' => 'assign',
  2234. 'instanceid' => $modinfo->instance,
  2235. )
  2236. ));
  2237. $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
  2238. other['name']");
  2239. } catch (coding_exception $e) {
  2240. $this->assertStringContainsString("The 'name' value must be set in other.", $e->getMessage());
  2241. }
  2242. }
  2243. /**
  2244. * Tests for event related to course module delete.
  2245. */
  2246. public function test_course_module_deleted_event() {
  2247. global $USER, $DB;
  2248. $this->resetAfterTest();
  2249. // Create and delete a module.
  2250. $sink = $this->redirectEvents();
  2251. $modinfo = $this->create_specific_module_test('forum');
  2252. $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
  2253. course_delete_module($modinfo->coursemodule);
  2254. $events = $sink->get_events();
  2255. $event = array_pop($events); // delete module event.;
  2256. $sink->close();
  2257. // Validate event data.
  2258. $this->assertInstanceOf('\core\event\course_module_deleted', $event);
  2259. $this->assertEquals($cm->id, $event->objectid);
  2260. $this->assertEquals($USER->id, $event->userid);
  2261. $this->assertEquals('course_modules', $event->objecttable);
  2262. $this->assertEquals(null, $event->get_url());
  2263. $this->assertEquals($cm, $event->get_record_snapshot('course_modules', $cm->id));
  2264. // Test legacy data.
  2265. $this->assertSame('mod_deleted', $event->get_legacy_eventname());
  2266. $eventdata = new stdClass();
  2267. $eventdata->modulename = 'forum';
  2268. $eventdata->cmid = $cm->id;
  2269. $eventdata->courseid = $cm->course;
  2270. $eventdata->userid = $USER->id;
  2271. $this->assertEventLegacyData($eventdata, $event);
  2272. $arr = array($cm->course, 'course', "delete mod", "view.php?id=$cm->course", "forum $cm->instance", $cm->id);
  2273. $this->assertEventLegacyLogData($arr, $event);
  2274. }
  2275. /**
  2276. * Tests for event validations related to course module deletion.
  2277. */
  2278. public function test_course_module_deleted_event_exceptions() {
  2279. $this->resetAfterTest();
  2280. // Generate data.
  2281. $modinfo = $this->create_specific_module_test('assign');
  2282. $context = context_module::instance($modinfo->coursemodule);
  2283. // Test not setting instanceid.
  2284. try {
  2285. $event = \core\event\course_module_deleted::create(array(
  2286. 'courseid' => $modinfo->course,
  2287. 'context' => $context,
  2288. 'objectid' => $modinfo->coursemodule,
  2289. 'other' => array(
  2290. 'modulename' => 'assign',
  2291. 'name' => 'My assignment',
  2292. )
  2293. ));
  2294. $this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without
  2295. other['instanceid']");
  2296. } catch (coding_exception $e) {
  2297. $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
  2298. }
  2299. // Test not setting modulename.
  2300. try {
  2301. $event = \core\event\course_module_deleted::create(array(
  2302. 'courseid' => $modinfo->course,
  2303. 'context' => $context,
  2304. 'objectid' => $modinfo->coursemodule,
  2305. 'other' => array(
  2306. 'instanceid' => $modinfo->instance,
  2307. 'name' => 'My assignment',
  2308. )
  2309. ));
  2310. $this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without
  2311. other['modulename']");
  2312. } catch (coding_exception $e) {
  2313. $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
  2314. }
  2315. }
  2316. /**
  2317. * Returns a user object and its assigned new role.
  2318. *
  2319. * @param testing_data_generator $generator
  2320. * @param $contextid
  2321. * @return array The user object and the role ID
  2322. */
  2323. protected function get_user_objects(testing_data_generator $generator, $contextid) {
  2324. global $USER;
  2325. if (empty($USER->id)) {
  2326. $user = $generator->create_user();
  2327. $this->setUser($user);
  2328. }
  2329. $roleid = create_role('Test role', 'testrole', 'Test role description');
  2330. if (!is_array($contextid)) {
  2331. $contextid = array($contextid);
  2332. }
  2333. foreach ($contextid as $cid) {
  2334. $assignid = role_assign($roleid, $user->id, $cid);
  2335. }
  2336. return array($user, $roleid);
  2337. }
  2338. /**
  2339. * Test course move after course.
  2340. */
  2341. public function test_course_change_sortorder_after_course() {
  2342. global $DB;
  2343. $this->resetAfterTest(true);
  2344. $generator = $this->getDataGenerator();
  2345. $category = $generator->create_category();
  2346. $course3 = $generator->create_course(array('category' => $category->id));
  2347. $course2 = $generator->create_course(array('category' => $category->id));
  2348. $course1 = $generator->create_course(array('category' => $category->id));
  2349. $context = $category->get_context();
  2350. list($user, $roleid) = $this->get_user_objects($generator, $context->id);
  2351. $caps = course_capability_assignment::allow('moodle/category:manage', $roleid, $context->id);
  2352. $courses = $category->get_courses();
  2353. $this->assertIsArray($courses);
  2354. $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
  2355. $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
  2356. $this->assertEquals(array_keys($dbcourses), array_keys($courses));
  2357. // Test moving down.
  2358. $this->assertTrue(course_change_sortorder_after_course($course1->id, $course3->id));
  2359. $courses = $category->get_courses();
  2360. $this->assertIsArray($courses);
  2361. $this->assertEquals(array($course2->id, $course3->id, $course1->id), array_keys($courses));
  2362. $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
  2363. $this->assertEquals(array_keys($dbcourses), array_keys($courses));
  2364. // Test moving up.
  2365. $this->assertTrue(course_change_sortorder_after_course($course1->id, $course2->id));
  2366. $courses = $category->get_courses();
  2367. $this->assertIsArray($courses);
  2368. $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses));
  2369. $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
  2370. $this->assertEquals(array_keys($dbcourses), array_keys($courses));
  2371. // Test moving to the top.
  2372. $this->assertTrue(course_change_sortorder_after_course($course1->id, 0));
  2373. $courses = $category->get_courses();
  2374. $this->assertIsArray($courses);
  2375. $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
  2376. $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
  2377. $this->assertEquals(array_keys($dbcourses), array_keys($courses));
  2378. }
  2379. /**
  2380. * Tests changing the visibility of a course.
  2381. */
  2382. public function test_course_change_visibility() {
  2383. global $DB;
  2384. $this->resetAfterTest(true);
  2385. $generator = $this->getDataGenerator();
  2386. $category = $generator->create_category();
  2387. $course = $generator->create_course(array('category' => $category->id));
  2388. $this->assertEquals('1', $course->visible);
  2389. $this->assertEquals('1', $course->visibleold);
  2390. $this->assertTrue(course_change_visibility($course->id, false));
  2391. $course = $DB->get_record('course', array('id' => $course->id));
  2392. $this->assertEquals('0', $course->visible);
  2393. $this->assertEquals('0', $course->visibleold);
  2394. $this->assertTrue(course_change_visibility($course->id, true));
  2395. $course = $DB->get_record('course', array('id' => $course->id));
  2396. $this->assertEquals('1', $course->visible);
  2397. $this->assertEquals('1', $course->visibleold);
  2398. }
  2399. /**
  2400. * Tests moving the course up and down by one.
  2401. */
  2402. public function test_course_change_sortorder_by_one() {
  2403. global $DB;
  2404. $this->resetAfterTest(true);
  2405. $generator = $this->getDataGenerator();
  2406. $category = $generator->create_category();
  2407. $course3 = $generator->create_course(array('category' => $category->id));
  2408. $course2 = $generator->create_course(array('category' => $category->id));
  2409. $course1 = $generator->create_course(array('category' => $category->id));
  2410. $courses = $category->get_courses();
  2411. $this->assertIsArray($courses);
  2412. $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
  2413. $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
  2414. $this->assertEquals(array_keys($dbcourses), array_keys($courses));
  2415. // Test moving down.
  2416. $course1 = get_course($course1->id);
  2417. $this->assertTrue(course_change_sortorder_by_one($course1, false));
  2418. $courses = $category->get_courses();
  2419. $this->assertIsArray($courses);
  2420. $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses));
  2421. $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
  2422. $this->assertEquals(array_keys($dbcourses), array_keys($courses));
  2423. // Test moving up.
  2424. $course1 = get_course($course1->id);
  2425. $this->assertTrue(course_change_sortorder_by_one($course1, true));
  2426. $courses = $category->get_courses();
  2427. $this->assertIsArray($courses);
  2428. $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
  2429. $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
  2430. $this->assertEquals(array_keys($dbcourses), array_keys($courses));
  2431. // Test moving the top course up one.
  2432. $course1 = get_course($course1->id);
  2433. $this->assertFalse(course_change_sortorder_by_one($course1, true));
  2434. // Check nothing changed.
  2435. $courses = $category->get_courses();
  2436. $this->assertIsArray($courses);
  2437. $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
  2438. $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
  2439. $this->assertEquals(array_keys($dbcourses), array_keys($courses));
  2440. // Test moving the bottom course up down.
  2441. $course3 = get_course($course3->id);
  2442. $this->assertFalse(course_change_sortorder_by_one($course3, false));
  2443. // Check nothing changed.
  2444. $courses = $category->get_courses();
  2445. $this->assertIsArray($courses);
  2446. $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
  2447. $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
  2448. $this->assertEquals(array_keys($dbcourses), array_keys($courses));
  2449. }
  2450. public function test_view_resources_list() {
  2451. $this->resetAfterTest();
  2452. $course = self::getDataGenerator()->create_course();
  2453. $coursecontext = context_course::instance($course->id);
  2454. $event = \core\event\course_resources_list_viewed::create(array('context' => context_course::instance($course->id)));
  2455. $event->set_legacy_logdata(array('book', 'page', 'resource'));
  2456. $sink = $this->redirectEvents();
  2457. $event->trigger();
  2458. $events = $sink->get_events();
  2459. $sink->close();
  2460. // Validate the event.
  2461. $event = $events[0];
  2462. $this->assertInstanceOf('\core\event\course_resources_list_viewed', $event);
  2463. $this->assertEquals(null, $event->objecttable);
  2464. $this->assertEquals(null, $event->objectid);
  2465. $this->assertEquals($course->id, $event->courseid);
  2466. $this->assertEquals($coursecontext->id, $event->contextid);
  2467. $expectedlegacydata = array(
  2468. array($course->id, "book", "view all", 'index.php?id=' . $course->id, ''),
  2469. array($course->id, "page", "view all", 'index.php?id=' . $course->id, ''),
  2470. array($course->id, "resource", "view all", 'index.php?id=' . $course->id, ''),
  2471. );
  2472. $this->assertEventLegacyLogData($expectedlegacydata, $event);
  2473. $this->assertEventContextNotUsed($event);
  2474. }
  2475. /**
  2476. * Test duplicate_module()
  2477. */
  2478. public function test_duplicate_module() {
  2479. $this->setAdminUser();
  2480. $this->resetAfterTest();
  2481. $course = self::getDataGenerator()->create_course();
  2482. $res = self::getDataGenerator()->create_module('resource', array('course' => $course));
  2483. $cm = get_coursemodule_from_id('resource', $res->cmid, 0, false, MUST_EXIST);
  2484. $newcm = duplicate_module($course, $cm);
  2485. // Make sure they are the same, except obvious id changes.
  2486. foreach ($cm as $prop => $value) {
  2487. if ($prop == 'id' || $prop == 'url' || $prop == 'instance' || $prop == 'added') {
  2488. // Ignore obviously different properties.
  2489. continue;
  2490. }
  2491. if ($prop == 'name') {
  2492. // We expect ' (copy)' to be added to the original name since MDL-59227.
  2493. $value = get_string('duplicatedmodule', 'moodle', $value);
  2494. }
  2495. $this->assertEquals($value, $newcm->$prop);
  2496. }
  2497. }
  2498. /**
  2499. * Tests that when creating or updating a module, if the availability settings
  2500. * are present but set to an empty tree, availability is set to null in
  2501. * database.
  2502. */
  2503. public function test_empty_availability_settings() {
  2504. global $DB;
  2505. $this->setAdminUser();
  2506. $this->resetAfterTest();
  2507. // Enable availability.
  2508. set_config('enableavailability', 1);
  2509. // Test add.
  2510. $emptyavailability = json_encode(\core_availability\tree::get_root_json(array()));
  2511. $course = self::getDataGenerator()->create_course();
  2512. $label = self::getDataGenerator()->create_module('label', array(
  2513. 'course' => $course, 'availability' => $emptyavailability));
  2514. $this->assertNull($DB->get_field('course_modules', 'availability',
  2515. array('id' => $label->cmid)));
  2516. // Test update.
  2517. $formdata = $DB->get_record('course_modules', array('id' => $label->cmid));
  2518. unset($formdata->availability);
  2519. $formdata->availabilityconditionsjson = $emptyavailability;
  2520. $formdata->modulename = 'label';
  2521. $formdata->coursemodule = $label->cmid;
  2522. $draftid = 0;
  2523. file_prepare_draft_area($draftid, context_module::instance($label->cmid)->id,
  2524. 'mod_label', 'intro', 0);
  2525. $formdata->introeditor = array(
  2526. 'itemid' => $draftid,
  2527. 'text' => '<p>Yo</p>',
  2528. 'format' => FORMAT_HTML);
  2529. update_module($formdata);
  2530. $this->assertNull($DB->get_field('course_modules', 'availability',
  2531. array('id' => $label->cmid)));
  2532. }
  2533. /**
  2534. * Test update_inplace_editable()
  2535. */
  2536. public function test_update_module_name_inplace() {
  2537. global $CFG, $DB, $PAGE;
  2538. require_once($CFG->dirroot . '/lib/external/externallib.php');
  2539. $this->setUser($this->getDataGenerator()->create_user());
  2540. $this->resetAfterTest(true);
  2541. $course = $this->getDataGenerator()->create_course();
  2542. $forum = self::getDataGenerator()->create_module('forum', array('course' => $course->id, 'name' => 'forum name'));
  2543. // Call service for core_course component without necessary permissions.
  2544. try {
  2545. core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name');
  2546. $this->fail('Exception expected');
  2547. } catch (moodle_exception $e) {
  2548. $this->assertEquals('Course or activity not accessible. (Not enrolled)',
  2549. $e->getMessage());
  2550. }
  2551. // Change to admin user and make sure that cm name can be updated using web service update_inplace_editable().
  2552. $this->setAdminUser();
  2553. $res = core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name');
  2554. $res = external_api::clean_returnvalue(core_external::update_inplace_editable_returns(), $res);
  2555. $this->assertEquals('New forum name', $res['value']);
  2556. $this->assertEquals('New forum name', $DB->get_field('forum', 'name', array('id' => $forum->id)));
  2557. }
  2558. /**
  2559. * Testing function course_get_tagged_course_modules - search tagged course modules
  2560. */
  2561. public function test_course_get_tagged_course_modules() {
  2562. global $DB;
  2563. $this->resetAfterTest();
  2564. $course3 = $this->getDataGenerator()->create_course();
  2565. $course2 = $this->getDataGenerator()->create_course();
  2566. $course1 = $this->getDataGenerator()->create_course();
  2567. $cm11 = $this->getDataGenerator()->create_module('assign', array('course' => $course1->id,
  2568. 'tags' => 'Cat, Dog'));
  2569. $cm12 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
  2570. 'tags' => 'Cat, Mouse', 'visible' => 0));
  2571. $cm13 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
  2572. 'tags' => 'Cat, Mouse, Dog'));
  2573. $cm21 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id,
  2574. 'tags' => 'Cat, Mouse'));
  2575. $cm31 = $this->getDataGenerator()->create_module('forum', array('course' => $course3->id,
  2576. 'tags' => 'Cat, Mouse'));
  2577. // Admin is able to view everything.
  2578. $this->setAdminUser();
  2579. $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
  2580. /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$page = */0);
  2581. $this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content);
  2582. $this->assertMatchesRegularExpression('/'.$cm12->name.'/', $res->content);
  2583. $this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content);
  2584. $this->assertMatchesRegularExpression('/'.$cm21->name.'/', $res->content);
  2585. $this->assertMatchesRegularExpression('/'.$cm31->name.'/', $res->content);
  2586. // Results from course1 are returned before results from course2.
  2587. $this->assertTrue(strpos($res->content, $cm11->name) < strpos($res->content, $cm21->name));
  2588. // Ordinary user is not able to see anything.
  2589. $user = $this->getDataGenerator()->create_user();
  2590. $this->setUser($user);
  2591. $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
  2592. /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$page = */0);
  2593. $this->assertNull($res);
  2594. // Enrol user as student in course1 and course2.
  2595. $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
  2596. $this->getDataGenerator()->enrol_user($user->id, $course1->id, $roleids['student']);
  2597. $this->getDataGenerator()->enrol_user($user->id, $course2->id, $roleids['student']);
  2598. core_tag_index_builder::reset_caches();
  2599. // Searching in the course context returns visible modules in this course.
  2600. $context = context_course::instance($course1->id);
  2601. $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
  2602. /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */0);
  2603. $this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content);
  2604. $this->assertDoesNotMatchRegularExpression('/'.$cm12->name.'/', $res->content);
  2605. $this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content);
  2606. $this->assertDoesNotMatchRegularExpression('/'.$cm21->name.'/', $res->content);
  2607. $this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content);
  2608. // Searching FROM the course context returns visible modules in all courses.
  2609. $context = context_course::instance($course2->id);
  2610. $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
  2611. /*$exclusivemode = */false, /*$fromctx = */$context->id, /*$ctx = */0, /*$rec = */1, /*$page = */0);
  2612. $this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content);
  2613. $this->assertDoesNotMatchRegularExpression('/'.$cm12->name.'/', $res->content);
  2614. $this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content);
  2615. $this->assertMatchesRegularExpression('/'.$cm21->name.'/', $res->content);
  2616. $this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); // No access to course3.
  2617. // Results from course2 are returned before results from course1.
  2618. $this->assertTrue(strpos($res->content, $cm21->name) < strpos($res->content, $cm11->name));
  2619. // Enrol user in course1 as a teacher - now he should be able to see hidden module.
  2620. $this->getDataGenerator()->enrol_user($user->id, $course1->id, $roleids['editingteacher']);
  2621. get_fast_modinfo(0,0,true);
  2622. $context = context_course::instance($course1->id);
  2623. $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
  2624. /*$exclusivemode = */false, /*$fromctx = */$context->id, /*$ctx = */0, /*$rec = */1, /*$page = */0);
  2625. $this->assertMatchesRegularExpression('/'.$cm12->name.'/', $res->content);
  2626. // Create more modules and try pagination.
  2627. $cm14 = $this->getDataGenerator()->create_module('assign', array('course' => $course1->id,
  2628. 'tags' => 'Cat, Dog'));
  2629. $cm15 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
  2630. 'tags' => 'Cat, Mouse', 'visible' => 0));
  2631. $cm16 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
  2632. 'tags' => 'Cat, Mouse, Dog'));
  2633. $context = context_course::instance($course1->id);
  2634. $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
  2635. /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */0);
  2636. $this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content);
  2637. $this->assertMatchesRegularExpression('/'.$cm12->name.'/', $res->content);
  2638. $this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content);
  2639. $this->assertDoesNotMatchRegularExpression('/'.$cm21->name.'/', $res->content);
  2640. $this->assertMatchesRegularExpression('/'.$cm14->name.'/', $res->content);
  2641. $this->assertMatchesRegularExpression('/'.$cm15->name.'/', $res->content);
  2642. $this->assertDoesNotMatchRegularExpression('/'.$cm16->name.'/', $res->content);
  2643. $this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); // No access to course3.
  2644. $this->assertEmpty($res->prevpageurl);
  2645. $this->assertNotEmpty($res->nextpageurl);
  2646. $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
  2647. /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */1);
  2648. $this->assertDoesNotMatchRegularExpression('/'.$cm11->name.'/', $res->content);
  2649. $this->assertDoesNotMatchRegularExpression('/'.$cm12->name.'/', $res->content);
  2650. $this->assertDoesNotMatchRegularExpression('/'.$cm13->name.'/', $res->content);
  2651. $this->assertDoesNotMatchRegularExpression('/'.$cm21->name.'/', $res->content);
  2652. $this->assertDoesNotMatchRegularExpression('/'.$cm14->name.'/', $res->content);
  2653. $this->assertDoesNotMatchRegularExpression('/'.$cm15->name.'/', $res->content);
  2654. $this->assertMatchesRegularExpression('/'.$cm16->name.'/', $res->content);
  2655. $this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); // No access to course3.
  2656. $this->assertNotEmpty($res->prevpageurl);
  2657. $this->assertEmpty($res->nextpageurl);
  2658. }
  2659. /**
  2660. * Test course_get_user_navigation_options for frontpage.
  2661. */
  2662. public function test_course_get_user_navigation_options_for_frontpage() {
  2663. global $CFG, $SITE, $DB;
  2664. $this->resetAfterTest();
  2665. $context = context_system::instance();
  2666. $course = clone $SITE;
  2667. $this->setAdminUser();
  2668. $navoptions = course_get_user_navigation_options($context, $course);
  2669. $this->assertTrue($navoptions->blogs);
  2670. $this->assertTrue($navoptions->notes);
  2671. $this->assertTrue($navoptions->participants);
  2672. $this->assertTrue($navoptions->badges);
  2673. $this->assertTrue($navoptions->tags);
  2674. $this->assertFalse($navoptions->search);
  2675. $this->assertTrue($navoptions->calendar);
  2676. $this->assertTrue($navoptions->competencies);
  2677. // Enable global search now.
  2678. $CFG->enableglobalsearch = 1;
  2679. $navoptions = course_get_user_navigation_options($context, $course);
  2680. $this->assertTrue($navoptions->search);
  2681. // Disable competencies.
  2682. $oldcompetencies = get_config('core_competency', 'enabled');
  2683. set_config('enabled', false, 'core_competency');
  2684. $navoptions = course_get_user_navigation_options($context, $course);
  2685. $this->assertFalse($navoptions->competencies);
  2686. set_config('enabled', $oldcompetencies, 'core_competency');
  2687. // Now try with a standard user.
  2688. $user = $this->getDataGenerator()->create_user();
  2689. $this->setUser($user);
  2690. $navoptions = course_get_user_navigation_options($context, $course);
  2691. $this->assertTrue($navoptions->blogs);
  2692. $this->assertFalse($navoptions->notes);
  2693. $this->assertFalse($navoptions->participants);
  2694. $this->assertTrue($navoptions->badges);
  2695. $this->assertTrue($navoptions->tags);
  2696. $this->assertTrue($navoptions->search);
  2697. $this->assertTrue($navoptions->calendar);
  2698. }
  2699. /**
  2700. * Test course_get_user_navigation_options for managers in a normal course.
  2701. */
  2702. public function test_course_get_user_navigation_options_for_managers() {
  2703. global $CFG;
  2704. $this->resetAfterTest();
  2705. $course = $this->getDataGenerator()->create_course();
  2706. $context = context_course::instance($course->id);
  2707. $this->setAdminUser();
  2708. $navoptions = course_get_user_navigation_options($context);
  2709. $this->assertTrue($navoptions->blogs);
  2710. $this->assertTrue($navoptions->notes);
  2711. $this->assertTrue($navoptions->participants);
  2712. $this->assertTrue($navoptions->badges);
  2713. }
  2714. /**
  2715. * Test course_get_user_navigation_options for students in a normal course.
  2716. */
  2717. public function test_course_get_user_navigation_options_for_students() {
  2718. global $DB, $CFG;
  2719. $this->resetAfterTest();
  2720. $course = $this->getDataGenerator()->create_course();
  2721. $context = context_course::instance($course->id);
  2722. $user = $this->getDataGenerator()->create_user();
  2723. $roleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
  2724. $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
  2725. $this->setUser($user);
  2726. $navoptions = course_get_user_navigation_options($context);
  2727. $this->assertTrue($navoptions->blogs);
  2728. $this->assertFalse($navoptions->notes);
  2729. $this->assertTrue($navoptions->participants);
  2730. $this->assertTrue($navoptions->badges);
  2731. // Disable some options.
  2732. $CFG->badges_allowcoursebadges = 0;
  2733. $CFG->enableblogs = 0;
  2734. // Disable view participants capability.
  2735. assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $context);
  2736. $navoptions = course_get_user_navigation_options($context);
  2737. $this->assertFalse($navoptions->blogs);
  2738. $this->assertFalse($navoptions->notes);
  2739. $this->assertFalse($navoptions->participants);
  2740. $this->assertFalse($navoptions->badges);
  2741. }
  2742. /**
  2743. * Test course_get_user_administration_options for frontpage.
  2744. */
  2745. public function test_course_get_user_administration_options_for_frontpage() {
  2746. global $CFG, $SITE;
  2747. $this->resetAfterTest();
  2748. $course = clone $SITE;
  2749. $context = context_course::instance($course->id);
  2750. $this->setAdminUser();
  2751. $adminoptions = course_get_user_administration_options($course, $context);
  2752. $this->assertTrue($adminoptions->update);
  2753. $this->assertTrue($adminoptions->filters);
  2754. $this->assertTrue($adminoptions->reports);
  2755. $this->assertTrue($adminoptions->backup);
  2756. $this->assertTrue($adminoptions->restore);
  2757. $this->assertFalse($adminoptions->files);
  2758. $this->assertFalse($adminoptions->tags);
  2759. // Now try with a standard user.
  2760. $user = $this->getDataGenerator()->create_user();
  2761. $this->setUser($user);
  2762. $adminoptions = course_get_user_administration_options($course, $context);
  2763. $this->assertFalse($adminoptions->update);
  2764. $this->assertFalse($adminoptions->filters);
  2765. $this->assertFalse($adminoptions->reports);
  2766. $this->assertFalse($adminoptions->backup);
  2767. $this->assertFalse($adminoptions->restore);
  2768. $this->assertFalse($adminoptions->files);
  2769. $this->assertFalse($adminoptions->tags);
  2770. }
  2771. /**
  2772. * Test course_get_user_administration_options for managers in a normal course.
  2773. */
  2774. public function test_course_get_user_administration_options_for_managers() {
  2775. global $CFG;
  2776. $this->resetAfterTest();
  2777. $course = $this->getDataGenerator()->create_course();
  2778. $context = context_course::instance($course->id);
  2779. $this->setAdminUser();
  2780. $adminoptions = course_get_user_administration_options($course, $context);
  2781. $this->assertTrue($adminoptions->update);
  2782. $this->assertTrue($adminoptions->filters);
  2783. $this->assertTrue($adminoptions->reports);
  2784. $this->assertTrue($adminoptions->backup);
  2785. $this->assertTrue($adminoptions->restore);
  2786. $this->assertFalse($adminoptions->files);
  2787. $this->assertTrue($adminoptions->tags);
  2788. $this->assertTrue($adminoptions->gradebook);
  2789. $this->assertFalse($adminoptions->outcomes);
  2790. $this->assertTrue($adminoptions->badges);
  2791. $this->assertTrue($adminoptions->import);
  2792. $this->assertTrue($adminoptions->reset);
  2793. $this->assertTrue($adminoptions->roles);
  2794. }
  2795. /**
  2796. * Test course_get_user_administration_options for students in a normal course.
  2797. */
  2798. public function test_course_get_user_administration_options_for_students() {
  2799. global $DB, $CFG;
  2800. $this->resetAfterTest();
  2801. $course = $this->getDataGenerator()->create_course();
  2802. $context = context_course::instance($course->id);
  2803. $user = $this->getDataGenerator()->create_user();
  2804. $roleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
  2805. $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
  2806. $this->setUser($user);
  2807. $adminoptions = course_get_user_administration_options($course, $context);
  2808. $this->assertFalse($adminoptions->update);
  2809. $this->assertFalse($adminoptions->filters);
  2810. $this->assertFalse($adminoptions->reports);
  2811. $this->assertFalse($adminoptions->backup);
  2812. $this->assertFalse($adminoptions->restore);
  2813. $this->assertFalse($adminoptions->files);
  2814. $this->assertFalse($adminoptions->tags);
  2815. $this->assertFalse($adminoptions->gradebook);
  2816. $this->assertFalse($adminoptions->outcomes);
  2817. $this->assertTrue($adminoptions->badges);
  2818. $this->assertFalse($adminoptions->import);
  2819. $this->assertFalse($adminoptions->reset);
  2820. $this->assertFalse($adminoptions->roles);
  2821. $CFG->enablebadges = false;
  2822. $adminoptions = course_get_user_administration_options($course, $context);
  2823. $this->assertFalse($adminoptions->badges);
  2824. }
  2825. /**
  2826. * Test test_update_course_frontpage_category.
  2827. */
  2828. public function test_update_course_frontpage_category() {
  2829. // Fetch front page course.
  2830. $course = get_course(SITEID);
  2831. // Test update information on front page course.
  2832. $course->category = 99;
  2833. $this->expectException('moodle_exception');
  2834. $this->expectExceptionMessage(get_string('invalidcourse', 'error'));
  2835. update_course($course);
  2836. }
  2837. /**
  2838. * test_course_enddate
  2839. *
  2840. * @dataProvider course_enddate_provider
  2841. * @param int $startdate
  2842. * @param int $enddate
  2843. * @param string $errorcode
  2844. */
  2845. public function test_course_enddate($startdate, $enddate, $errorcode) {
  2846. $this->resetAfterTest(true);
  2847. $record = array('startdate' => $startdate, 'enddate' => $enddate);
  2848. try {
  2849. $course1 = $this->getDataGenerator()->create_course($record);
  2850. if ($errorcode !== false) {
  2851. $this->fail('Expected exception with "' . $errorcode . '" error code in create_create');
  2852. }
  2853. } catch (moodle_exception $e) {
  2854. if ($errorcode === false) {
  2855. $this->fail('Got "' . $errorcode . '" exception error code and no exception was expected');
  2856. }
  2857. if ($e->errorcode != $errorcode) {
  2858. $this->fail('Got "' . $e->errorcode. '" exception error code and "' . $errorcode . '" was expected');
  2859. }
  2860. return;
  2861. }
  2862. $this->assertEquals($startdate, $course1->startdate);
  2863. $this->assertEquals($enddate, $course1->enddate);
  2864. }
  2865. /**
  2866. * Provider for test_course_enddate.
  2867. *
  2868. * @return array
  2869. */
  2870. public function course_enddate_provider() {
  2871. // Each provided example contains startdate, enddate and the expected exception error code if there is any.
  2872. return [
  2873. [
  2874. 111,
  2875. 222,
  2876. false
  2877. ], [
  2878. 222,
  2879. 111,
  2880. 'enddatebeforestartdate'
  2881. ], [
  2882. 111,
  2883. 0,
  2884. false
  2885. ], [
  2886. 0,
  2887. 222,
  2888. 'nostartdatenoenddate'
  2889. ]
  2890. ];
  2891. }
  2892. /**
  2893. * test_course_dates_reset
  2894. *
  2895. * @dataProvider course_dates_reset_provider
  2896. * @param int $startdate
  2897. * @param int $enddate
  2898. * @param int $resetstartdate
  2899. * @param int $resetenddate
  2900. * @param int $resultingstartdate
  2901. * @param int $resultingenddate
  2902. */
  2903. public function test_course_dates_reset($startdate, $enddate, $resetstartdate, $resetenddate, $resultingstartdate, $resultingenddate) {
  2904. global $CFG, $DB;
  2905. require_once($CFG->dirroot.'/completion/criteria/completion_criteria_date.php');
  2906. $this->resetAfterTest(true);
  2907. $this->setAdminUser();
  2908. $CFG->enablecompletion = true;
  2909. $this->setTimezone('UTC');
  2910. $record = array('startdate' => $startdate, 'enddate' => $enddate, 'enablecompletion' => 1);
  2911. $originalcourse = $this->getDataGenerator()->create_course($record);
  2912. $coursecriteria = new completion_criteria_date(array('course' => $originalcourse->id, 'timeend' => $startdate + DAYSECS));
  2913. $coursecriteria->insert();
  2914. $activitycompletiondate = $startdate + DAYSECS;
  2915. $data = $this->getDataGenerator()->create_module('data', array('course' => $originalcourse->id),
  2916. array('completion' => 1, 'completionexpected' => $activitycompletiondate));
  2917. $resetdata = new stdClass();
  2918. $resetdata->id = $originalcourse->id;
  2919. $resetdata->reset_start_date_old = $originalcourse->startdate;
  2920. $resetdata->reset_start_date = $resetstartdate;
  2921. $resetdata->reset_end_date = $resetenddate;
  2922. $resetdata->reset_end_date_old = $record['enddate'];
  2923. reset_course_userdata($resetdata);
  2924. $course = $DB->get_record('course', array('id' => $originalcourse->id));
  2925. $this->assertEquals($resultingstartdate, $course->startdate);
  2926. $this->assertEquals($resultingenddate, $course->enddate);
  2927. $coursecompletioncriteria = completion_criteria_date::fetch(array('course' => $originalcourse->id));
  2928. $this->assertEquals($resultingstartdate + DAYSECS, $coursecompletioncriteria->timeend);
  2929. $this->assertEquals($resultingstartdate + DAYSECS, $DB->get_field('course_modules', 'completionexpected',
  2930. array('id' => $data->cmid)));
  2931. }
  2932. /**
  2933. * Provider for test_course_dates_reset.
  2934. *
  2935. * @return array
  2936. */
  2937. public function course_dates_reset_provider() {
  2938. // Each example contains the following:
  2939. // - course startdate
  2940. // - course enddate
  2941. // - startdate to reset to (false if not reset)
  2942. // - enddate to reset to (false if not reset)
  2943. // - resulting startdate
  2944. // - resulting enddate
  2945. $time = 1445644800;
  2946. return [
  2947. // No date changes.
  2948. [
  2949. $time,
  2950. $time + DAYSECS,
  2951. false,
  2952. false,
  2953. $time,
  2954. $time + DAYSECS
  2955. ],
  2956. // End date changes to a valid value.
  2957. [
  2958. $time,
  2959. $time + DAYSECS,
  2960. false,
  2961. $time + DAYSECS + 111,
  2962. $time,
  2963. $time + DAYSECS + 111
  2964. ],
  2965. // Start date changes to a valid value. End date does not get updated because it does not have value.
  2966. [
  2967. $time,
  2968. 0,
  2969. $time + DAYSECS,
  2970. false,
  2971. $time + DAYSECS,
  2972. 0
  2973. ],
  2974. // Start date changes to a valid value. End date gets updated accordingly.
  2975. [
  2976. $time,
  2977. $time + DAYSECS,
  2978. $time + WEEKSECS,
  2979. false,
  2980. $time + WEEKSECS,
  2981. $time + WEEKSECS + DAYSECS
  2982. ],
  2983. // Start date and end date change to a valid value.
  2984. [
  2985. $time,
  2986. $time + DAYSECS,
  2987. $time + WEEKSECS,
  2988. $time + YEARSECS,
  2989. $time + WEEKSECS,
  2990. $time + YEARSECS
  2991. ]
  2992. ];
  2993. }
  2994. /**
  2995. * Test reset_course_userdata()
  2996. * - with reset_roles_overrides enabled
  2997. * - with selective role unenrolments
  2998. */
  2999. public function test_course_roles_reset() {
  3000. global $DB;
  3001. $this->resetAfterTest(true);
  3002. $generator = $this->getDataGenerator();
  3003. // Create test course and user, enrol one in the other.
  3004. $course = $generator->create_course();
  3005. $user = $generator->create_user();
  3006. $roleid = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
  3007. $generator->enrol_user($user->id, $course->id, $roleid);
  3008. // Test case with reset_roles_overrides enabled.
  3009. // Override course so it does NOT allow students 'mod/forum:viewdiscussion'.
  3010. $coursecontext = context_course::instance($course->id);
  3011. assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $roleid, $coursecontext->id);
  3012. // Check expected capabilities so far.
  3013. $this->assertFalse(has_capability('mod/forum:viewdiscussion', $coursecontext, $user));
  3014. // Oops, preventing student from viewing forums was a mistake, let's reset the course.
  3015. $resetdata = new stdClass();
  3016. $resetdata->id = $course->id;
  3017. $resetdata->reset_roles_overrides = true;
  3018. reset_course_userdata($resetdata);
  3019. // Check new expected capabilities - override at the course level should be reset.
  3020. $this->assertTrue(has_capability('mod/forum:viewdiscussion', $coursecontext, $user));
  3021. // Test case with selective role unenrolments.
  3022. $roles = array();
  3023. $roles['student'] = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
  3024. $roles['teacher'] = $DB->get_field('role', 'id', array('shortname' => 'teacher'), MUST_EXIST);
  3025. // We enrol a user with student and teacher roles.
  3026. $generator->enrol_user($user->id, $course->id, $roles['student']);
  3027. $generator->enrol_user($user->id, $course->id, $roles['teacher']);
  3028. // When we reset only student role, we expect to keep teacher role.
  3029. $resetdata = new stdClass();
  3030. $resetdata->id = $course->id;
  3031. $resetdata->unenrol_users = array($roles['student']);
  3032. reset_course_userdata($resetdata);
  3033. $usersroles = enrol_get_course_users_roles($course->id);
  3034. $this->assertArrayHasKey($user->id, $usersroles);
  3035. $this->assertArrayHasKey($roles['teacher'], $usersroles[$user->id]);
  3036. $this->assertArrayNotHasKey($roles['student'], $usersroles[$user->id]);
  3037. $this->assertCount(1, $usersroles[$user->id]);
  3038. // We reenrol user as student.
  3039. $generator->enrol_user($user->id, $course->id, $roles['student']);
  3040. // When we reset student and teacher roles, we expect no roles left.
  3041. $resetdata = new stdClass();
  3042. $resetdata->id = $course->id;
  3043. $resetdata->unenrol_users = array($roles['student'], $roles['teacher']);
  3044. reset_course_userdata($resetdata);
  3045. $usersroles = enrol_get_course_users_roles($course->id);
  3046. $this->assertEmpty($usersroles);
  3047. }
  3048. public function test_course_check_module_updates_since() {
  3049. global $CFG, $DB, $USER;
  3050. require_once($CFG->dirroot . '/mod/glossary/lib.php');
  3051. require_once($CFG->dirroot . '/rating/lib.php');
  3052. require_once($CFG->dirroot . '/comment/lib.php');
  3053. $this->resetAfterTest(true);
  3054. $CFG->enablecompletion = true;
  3055. $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
  3056. $glossary = $this->getDataGenerator()->create_module('glossary', array(
  3057. 'course' => $course->id,
  3058. 'completion' => COMPLETION_TRACKING_AUTOMATIC,
  3059. 'completionview' => 1,
  3060. 'allowcomments' => 1,
  3061. 'assessed' => RATING_AGGREGATE_AVERAGE,
  3062. 'scale' => 100
  3063. ));
  3064. $glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
  3065. $context = context_module::instance($glossary->cmid);
  3066. $modinfo = get_fast_modinfo($course);
  3067. $cm = $modinfo->get_cm($glossary->cmid);
  3068. $user = $this->getDataGenerator()->create_user();
  3069. $studentrole = $DB->get_record('role', array('shortname' => 'student'));
  3070. $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
  3071. $from = time();
  3072. $teacher = $this->getDataGenerator()->create_user();
  3073. $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
  3074. $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
  3075. assign_capability('mod/glossary:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id, true);
  3076. // Check nothing changed right now.
  3077. $updates = course_check_module_updates_since($cm, $from);
  3078. $this->assertFalse($updates->configuration->updated);
  3079. $this->assertFalse($updates->completion->updated);
  3080. $this->assertFalse($updates->gradeitems->updated);
  3081. $this->assertFalse($updates->comments->updated);
  3082. $this->assertFalse($updates->ratings->updated);
  3083. $this->assertFalse($updates->introfiles->updated);
  3084. $this->assertFalse($updates->outcomes->updated);
  3085. $this->waitForSecond();
  3086. // Do some changes.
  3087. $this->setUser($user);
  3088. $entry = $glossarygenerator->create_content($glossary);
  3089. $this->setUser($teacher);
  3090. // Name.
  3091. set_coursemodule_name($glossary->cmid, 'New name');
  3092. // Add some ratings.
  3093. $rm = new rating_manager();
  3094. $result = $rm->add_rating($cm, $context, 'mod_glossary', 'entry', $entry->id, 100, 50, $user->id, RATING_AGGREGATE_AVERAGE);
  3095. // Change grades.
  3096. $glossary->cmidnumber = $glossary->cmid;
  3097. glossary_update_grades($glossary, $user->id);
  3098. $this->setUser($user);
  3099. // Completion status.
  3100. glossary_view($glossary, $course, $cm, $context, 'letter');
  3101. // Add one comment.
  3102. $args = new stdClass;
  3103. $args->context = $context;
  3104. $args->course = $course;
  3105. $args->cm = $cm;
  3106. $args->area = 'glossary_entry';
  3107. $args->itemid = $entry->id;
  3108. $args->client_id = 1;
  3109. $args->component = 'mod_glossary';
  3110. $manager = new comment($args);
  3111. $manager->add('blah blah blah');
  3112. // Check upgrade status.
  3113. $updates = course_check_module_updates_since($cm, $from);
  3114. $this->assertTrue($updates->configuration->updated);
  3115. $this->assertTrue($updates->completion->updated);
  3116. $this->assertTrue($updates->gradeitems->updated);
  3117. $this->assertTrue($updates->comments->updated);
  3118. $this->assertTrue($updates->ratings->updated);
  3119. $this->assertFalse($updates->introfiles->updated);
  3120. $this->assertFalse($updates->outcomes->updated);
  3121. }
  3122. public function test_async_module_deletion_hook_implemented() {
  3123. // Async module deletion depends on the 'true' being returned by at least one plugin implementing the hook,
  3124. // 'course_module_adhoc_deletion_recommended'. In core, is implemented by the course recyclebin, which will only return
  3125. // true if the recyclebin plugin is enabled. To make sure async deletion occurs, this test force-enables the recyclebin.
  3126. global $DB, $USER;
  3127. $this->resetAfterTest(true);
  3128. $this->setAdminUser();
  3129. // Ensure recyclebin is enabled.
  3130. set_config('coursebinenable', true, 'tool_recyclebin');
  3131. // Create course, module and context.
  3132. $course = $this->getDataGenerator()->create_course(['numsections' => 5]);
  3133. $module = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
  3134. $modcontext = context_module::instance($module->cmid);
  3135. // Verify context exists.
  3136. $this->assertInstanceOf('context_module', $modcontext);
  3137. // Check events generated on the course_delete_module call.
  3138. $sink = $this->redirectEvents();
  3139. // Try to delete the module using the async flag.
  3140. course_delete_module($module->cmid, true); // Try to delete the module asynchronously.
  3141. // Verify that no event has been generated yet.
  3142. $events = $sink->get_events();
  3143. $event = array_pop($events);
  3144. $sink->close();
  3145. $this->assertEmpty($event);
  3146. // Grab the record, in it's final state before hard deletion, for comparison with the event snapshot.
  3147. // We need to do this because the 'deletioninprogress' flag has changed from '0' to '1'.
  3148. $cm = $DB->get_record('course_modules', ['id' => $module->cmid], '*', MUST_EXIST);
  3149. // Verify the course_module is marked as 'deletioninprogress'.
  3150. $this->assertNotEquals($cm, false);
  3151. $this->assertEquals($cm->deletioninprogress, '1');
  3152. // Verify the context has not yet been removed.
  3153. $this->assertEquals($modcontext, context_module::instance($module->cmid, IGNORE_MISSING));
  3154. // Set up a sink to catch the 'course_module_deleted' event.
  3155. $sink = $this->redirectEvents();
  3156. // Now, run the adhoc task which performs the hard deletion.
  3157. phpunit_util::run_all_adhoc_tasks();
  3158. // Fetch and validate the event data.
  3159. $events = $sink->get_events();
  3160. $event = array_pop($events);
  3161. $sink->close();
  3162. $this->assertInstanceOf('\core\event\course_module_deleted', $event);
  3163. $this->assertEquals($module->cmid, $event->objectid);
  3164. $this->assertEquals($USER->id, $event->userid);
  3165. $this->assertEquals('course_modules', $event->objecttable);
  3166. $this->assertEquals(null, $event->get_url());
  3167. $this->assertEquals($cm, $event->get_record_snapshot('course_modules', $module->cmid));
  3168. // Verify the context has been removed.
  3169. $this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING));
  3170. // Verify the course_module record has been deleted.
  3171. $cmcount = $DB->count_records('course_modules', ['id' => $module->cmid]);
  3172. $this->assertEmpty($cmcount);
  3173. }
  3174. public function test_async_module_deletion_hook_not_implemented() {
  3175. // Only proceed if we are sure that no plugin is going to advocate async removal of a module. I.e. no plugin returns
  3176. // 'true' from the 'course_module_adhoc_deletion_recommended' hook.
  3177. // In the case of core, only recyclebin implements this hook, and it will only return true if enabled, so disable it.
  3178. global $DB, $USER;
  3179. $this->resetAfterTest(true);
  3180. $this->setAdminUser();
  3181. set_config('coursebinenable', false, 'tool_recyclebin');
  3182. // Non-core plugins might implement the 'course_module_adhoc_deletion_recommended' hook and spoil this test.
  3183. // If at least one plugin still returns true, then skip this test.
  3184. if ($pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) {
  3185. foreach ($pluginsfunction as $plugintype => $plugins) {
  3186. foreach ($plugins as $pluginfunction) {
  3187. if ($pluginfunction()) {
  3188. $this->markTestSkipped();
  3189. }
  3190. }
  3191. }
  3192. }
  3193. // Create course, module and context.
  3194. $course = $this->getDataGenerator()->create_course(['numsections' => 5]);
  3195. $module = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
  3196. $modcontext = context_module::instance($module->cmid);
  3197. $cm = $DB->get_record('course_modules', ['id' => $module->cmid], '*', MUST_EXIST);
  3198. // Verify context exists.
  3199. $this->assertInstanceOf('context_module', $modcontext);
  3200. // Check events generated on the course_delete_module call.
  3201. $sink = $this->redirectEvents();
  3202. // Try to delete the module using the async flag.
  3203. course_delete_module($module->cmid, true); // Try to delete the module asynchronously.
  3204. // Fetch and validate the event data.
  3205. $events = $sink->get_events();
  3206. $event = array_pop($events);
  3207. $sink->close();
  3208. $this->assertInstanceOf('\core\event\course_module_deleted', $event);
  3209. $this->assertEquals($module->cmid, $event->objectid);
  3210. $this->assertEquals($USER->id, $event->userid);
  3211. $this->assertEquals('course_modules', $event->objecttable);
  3212. $this->assertEquals(null, $event->get_url());
  3213. $this->assertEquals($cm, $event->get_record_snapshot('course_modules', $module->cmid));
  3214. // Verify the context has been removed.
  3215. $this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING));
  3216. // Verify the course_module record has been deleted.
  3217. $cmcount = $DB->count_records('course_modules', ['id' => $module->cmid]);
  3218. $this->assertEmpty($cmcount);
  3219. }
  3220. public function test_async_section_deletion_hook_implemented() {
  3221. // Async section deletion (provided section contains modules), depends on the 'true' being returned by at least one plugin
  3222. // implementing the 'course_module_adhoc_deletion_recommended' hook. In core, is implemented by the course recyclebin,
  3223. // which will only return true if the plugin is enabled. To make sure async deletion occurs, this test enables recyclebin.
  3224. global $DB, $USER;
  3225. $this->resetAfterTest(true);
  3226. $this->setAdminUser();
  3227. // Ensure recyclebin is enabled.
  3228. set_config('coursebinenable', true, 'tool_recyclebin');
  3229. // Create course, module and context.
  3230. $generator = $this->getDataGenerator();
  3231. $course = $generator->create_course(['numsections' => 4, 'format' => 'topics'], ['createsections' => true]);
  3232. $assign0 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
  3233. $assign1 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
  3234. $assign2 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
  3235. $assign3 = $generator->create_module('assign', ['course' => $course, 'section' => 0]);
  3236. // Delete empty section. No difference from normal, synchronous behaviour.
  3237. $this->assertTrue(course_delete_section($course, 4, false, true));
  3238. $this->assertEquals(3, course_get_format($course)->get_last_section_number());
  3239. // Delete a module in section 2 (using async). Need to verify this doesn't generate two tasks when we delete
  3240. // the section in the next step.
  3241. course_delete_module($assign2->cmid, true);
  3242. // Confirm that the module is pending deletion in its current section.
  3243. $section = $DB->get_record('course_sections', ['course' => $course->id, 'section' => '2']); // For event comparison.
  3244. $this->assertEquals(true, $DB->record_exists('course_modules', ['id' => $assign2->cmid, 'deletioninprogress' => 1,
  3245. 'section' => $section->id]));
  3246. // Now, delete section 2.
  3247. $this->assertFalse(course_delete_section($course, 2, false, true)); // Non-empty section, no forcedelete, so no change.
  3248. $sink = $this->redirectEvents(); // To capture the event.
  3249. $this->assertTrue(course_delete_section($course, 2, true, true));
  3250. // Now, confirm that:
  3251. // a) the section's modules have been flagged for deletion and moved to section 0 and;
  3252. // b) the section has been deleted and;
  3253. // c) course_section_deleted event has been fired. The course_module_deleted events will only fire once they have been
  3254. // removed from section 0 via the adhoc task.
  3255. // Modules should have been flagged for deletion and moved to section 0.
  3256. $sectionid = $DB->get_field('course_sections', 'id', ['course' => $course->id, 'section' => 0]);
  3257. $this->assertEquals(3, $DB->count_records('course_modules', ['section' => $sectionid, 'deletioninprogress' => 1]));
  3258. // Confirm the section has been deleted.
  3259. $this->assertEquals(2, course_get_format($course)->get_last_section_number());
  3260. // Check event fired.
  3261. $events = $sink->get_events();
  3262. $event = array_pop($events);
  3263. $sink->close();
  3264. $this->assertInstanceOf('\core\event\course_section_deleted', $event);
  3265. $this->assertEquals($section->id, $event->objectid);
  3266. $this->assertEquals($USER->id, $event->userid);
  3267. $this->assertEquals('course_sections', $event->objecttable);
  3268. $this->assertEquals(null, $event->get_url());
  3269. $this->assertEquals($section, $event->get_record_snapshot('course_sections', $section->id));
  3270. // Now, run the adhoc task to delete the modules from section 0.
  3271. $sink = $this->redirectEvents(); // To capture the events.
  3272. phpunit_util::run_all_adhoc_tasks();
  3273. // Confirm the modules have been deleted.
  3274. list($insql, $assignids) = $DB->get_in_or_equal([$assign0->cmid, $assign1->cmid, $assign2->cmid]);
  3275. $cmcount = $DB->count_records_select('course_modules', 'id ' . $insql, $assignids);
  3276. $this->assertEmpty($cmcount);
  3277. // Confirm other modules in section 0 still remain.
  3278. $this->assertEquals(1, $DB->count_records('course_modules', ['id' => $assign3->cmid]));
  3279. // Confirm that events were generated for all 3 of the modules.
  3280. $events = $sink->get_events();
  3281. $sink->close();
  3282. $count = 0;
  3283. while (!empty($events)) {
  3284. $event = array_pop($events);
  3285. if ($event instanceof \core\event\course_module_deleted &&
  3286. in_array($event->objectid, [$assign0->cmid, $assign1->cmid, $assign2->cmid])) {
  3287. $count++;
  3288. }
  3289. }
  3290. $this->assertEquals(3, $count);
  3291. }
  3292. public function test_async_section_deletion_hook_not_implemented() {
  3293. // If no plugins advocate async removal, then normal synchronous removal will take place.
  3294. // Only proceed if we are sure that no plugin is going to advocate async removal of a module. I.e. no plugin returns
  3295. // 'true' from the 'course_module_adhoc_deletion_recommended' hook.
  3296. // In the case of core, only recyclebin implements this hook, and it will only return true if enabled, so disable it.
  3297. global $DB, $USER;
  3298. $this->resetAfterTest(true);
  3299. $this->setAdminUser();
  3300. set_config('coursebinenable', false, 'tool_recyclebin');
  3301. // Non-core plugins might implement the 'course_module_adhoc_deletion_recommended' hook and spoil this test.
  3302. // If at least one plugin still returns true, then skip this test.
  3303. if ($pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) {
  3304. foreach ($pluginsfunction as $plugintype => $plugins) {
  3305. foreach ($plugins as $pluginfunction) {
  3306. if ($pluginfunction()) {
  3307. $this->markTestSkipped();
  3308. }
  3309. }
  3310. }
  3311. }
  3312. // Create course, module and context.
  3313. $generator = $this->getDataGenerator();
  3314. $course = $generator->create_course(['numsections' => 4, 'format' => 'topics'], ['createsections' => true]);
  3315. $assign0 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
  3316. $assign1 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
  3317. // Delete empty section. No difference from normal, synchronous behaviour.
  3318. $this->assertTrue(course_delete_section($course, 4, false, true));
  3319. $this->assertEquals(3, course_get_format($course)->get_last_section_number());
  3320. // Delete section in the middle (2).
  3321. $section = $DB->get_record('course_sections', ['course' => $course->id, 'section' => '2']); // For event comparison.
  3322. $this->assertFalse(course_delete_section($course, 2, false, true)); // Non-empty section, no forcedelete, so no change.
  3323. $sink = $this->redirectEvents(); // To capture the event.
  3324. $this->assertTrue(course_delete_section($course, 2, true, true));
  3325. // Now, confirm that:
  3326. // a) The section's modules have deleted and;
  3327. // b) the section has been deleted and;
  3328. // c) course_section_deleted event has been fired and;
  3329. // d) course_module_deleted events have both been fired.
  3330. // Confirm modules have been deleted.
  3331. list($insql, $assignids) = $DB->get_in_or_equal([$assign0->cmid, $assign1->cmid]);
  3332. $cmcount = $DB->count_records_select('course_modules', 'id ' . $insql, $assignids);
  3333. $this->assertEmpty($cmcount);
  3334. // Confirm the section has been deleted.
  3335. $this->assertEquals(2, course_get_format($course)->get_last_section_number());
  3336. // Confirm the course_section_deleted event has been generated.
  3337. $events = $sink->get_events();
  3338. $event = array_pop($events);
  3339. $sink->close();
  3340. $this->assertInstanceOf('\core\event\course_section_deleted', $event);
  3341. $this->assertEquals($section->id, $event->objectid);
  3342. $this->assertEquals($USER->id, $event->userid);
  3343. $this->assertEquals('course_sections', $event->objecttable);
  3344. $this->assertEquals(null, $event->get_url());
  3345. $this->assertEquals($section, $event->get_record_snapshot('course_sections', $section->id));
  3346. // Confirm that the course_module_deleted events have both been generated.
  3347. $count = 0;
  3348. while (!empty($events)) {
  3349. $event = array_pop($events);
  3350. if ($event instanceof \core\event\course_module_deleted &&
  3351. in_array($event->objectid, [$assign0->cmid, $assign1->cmid])) {
  3352. $count++;
  3353. }
  3354. }
  3355. $this->assertEquals(2, $count);
  3356. }
  3357. public function test_classify_course_for_timeline() {
  3358. global $DB, $CFG;
  3359. require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
  3360. set_config('enablecompletion', COMPLETION_ENABLED);
  3361. set_config('coursegraceperiodbefore', 0);
  3362. set_config('coursegraceperiodafter', 0);
  3363. $this->resetAfterTest(true);
  3364. $this->setAdminUser();
  3365. // Create courses for testing.
  3366. $generator = $this->getDataGenerator();
  3367. $future = time() + 3600;
  3368. $past = time() - 3600;
  3369. $futurecourse = $generator->create_course(['startdate' => $future]);
  3370. $pastcourse = $generator->create_course(['startdate' => $past - 60, 'enddate' => $past]);
  3371. $completedcourse = $generator->create_course(['enablecompletion' => COMPLETION_ENABLED]);
  3372. $inprogresscourse = $generator->create_course();
  3373. // Set completion rules.
  3374. $criteriadata = new stdClass();
  3375. $criteriadata->id = $completedcourse->id;
  3376. // Self completion.
  3377. $criteriadata->criteria_self = COMPLETION_CRITERIA_TYPE_SELF;
  3378. $class = 'completion_criteria_self';
  3379. $criterion = new $class();
  3380. $criterion->update_config($criteriadata);
  3381. $user = $this->getDataGenerator()->create_user();
  3382. $studentrole = $DB->get_record('role', array('shortname' => 'student'));
  3383. $this->getDataGenerator()->enrol_user($user->id, $futurecourse->id, $studentrole->id);
  3384. $this->getDataGenerator()->enrol_user($user->id, $pastcourse->id, $studentrole->id);
  3385. $this->getDataGenerator()->enrol_user($user->id, $completedcourse->id, $studentrole->id);
  3386. $this->getDataGenerator()->enrol_user($user->id, $inprogresscourse->id, $studentrole->id);
  3387. $this->setUser($user);
  3388. core_completion_external::mark_course_self_completed($completedcourse->id);
  3389. $ccompletion = new completion_completion(array('course' => $completedcourse->id, 'userid' => $user->id));
  3390. $ccompletion->mark_complete();
  3391. // Aggregate the completions.
  3392. $this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($pastcourse));
  3393. $this->assertEquals(COURSE_TIMELINE_FUTURE, course_classify_for_timeline($futurecourse));
  3394. $this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($completedcourse));
  3395. $this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($inprogresscourse));
  3396. // Test grace period.
  3397. set_config('coursegraceperiodafter', 1);
  3398. set_config('coursegraceperiodbefore', 1);
  3399. $this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($pastcourse));
  3400. $this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($futurecourse));
  3401. $this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($completedcourse));
  3402. $this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($inprogresscourse));
  3403. }
  3404. /**
  3405. * Test the main function for updating all calendar events for a module.
  3406. */
  3407. public function test_course_module_calendar_event_update_process() {
  3408. global $DB;
  3409. $this->resetAfterTest();
  3410. $this->setAdminUser();
  3411. $completionexpected = time();
  3412. $duedate = time();
  3413. $course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
  3414. $assign = $this->getDataGenerator()->create_module('assign', [
  3415. 'course' => $course,
  3416. 'completionexpected' => $completionexpected,
  3417. 'duedate' => $duedate
  3418. ]);
  3419. $cm = get_coursemodule_from_instance('assign', $assign->id, $course->id);
  3420. $events = $DB->get_records('event', ['courseid' => $course->id, 'instance' => $assign->id]);
  3421. // Check that both events are using the expected dates.
  3422. foreach ($events as $event) {
  3423. if ($event->eventtype == \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
  3424. $this->assertEquals($completionexpected, $event->timestart);
  3425. }
  3426. if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
  3427. $this->assertEquals($duedate, $event->timestart);
  3428. }
  3429. }
  3430. // We have to manually update the module and the course module.
  3431. $newcompletionexpected = time() + DAYSECS * 60;
  3432. $newduedate = time() + DAYSECS * 45;
  3433. $newmodulename = 'Assign - new name';
  3434. $moduleobject = (object)array('id' => $assign->id, 'duedate' => $newduedate, 'name' => $newmodulename);
  3435. $DB->update_record('assign', $moduleobject);
  3436. $cmobject = (object)array('id' => $cm->id, 'completionexpected' => $newcompletionexpected);
  3437. $DB->update_record('course_modules', $cmobject);
  3438. $assign = $DB->get_record('assign', ['id' => $assign->id]);
  3439. $cm = get_coursemodule_from_instance('assign', $assign->id, $course->id);
  3440. course_module_calendar_event_update_process($assign, $cm);
  3441. $events = $DB->get_records('event', ['courseid' => $course->id, 'instance' => $assign->id]);
  3442. // Now check that the details have been updated properly from the function.
  3443. foreach ($events as $event) {
  3444. if ($event->eventtype == \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
  3445. $this->assertEquals($newcompletionexpected, $event->timestart);
  3446. $this->assertEquals(get_string('completionexpectedfor', 'completion', (object)['instancename' => $newmodulename]),
  3447. $event->name);
  3448. }
  3449. if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
  3450. $this->assertEquals($newduedate, $event->timestart);
  3451. $this->assertEquals(get_string('calendardue', 'assign', $newmodulename), $event->name);
  3452. }
  3453. }
  3454. }
  3455. /**
  3456. * Test the higher level checks for updating calendar events for an instance.
  3457. */
  3458. public function test_course_module_update_calendar_events() {
  3459. $this->resetAfterTest();
  3460. $this->setAdminUser();
  3461. $completionexpected = time();
  3462. $duedate = time();
  3463. $course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
  3464. $assign = $this->getDataGenerator()->create_module('assign', [
  3465. 'course' => $course,
  3466. 'completionexpected' => $completionexpected,
  3467. 'duedate' => $duedate
  3468. ]);
  3469. $cm = get_coursemodule_from_instance('assign', $assign->id, $course->id);
  3470. // Both the instance and cm objects are missing.
  3471. $this->assertFalse(course_module_update_calendar_events('assign'));
  3472. // Just using the assign instance.
  3473. $this->assertTrue(course_module_update_calendar_events('assign', $assign));
  3474. // Just using the course module object.
  3475. $this->assertTrue(course_module_update_calendar_events('assign', null, $cm));
  3476. // Using both the assign instance and the course module object.
  3477. $this->assertTrue(course_module_update_calendar_events('assign', $assign, $cm));
  3478. }
  3479. /**
  3480. * Test the higher level checks for updating calendar events for a module.
  3481. */
  3482. public function test_course_module_bulk_update_calendar_events() {
  3483. $this->resetAfterTest();
  3484. $this->setAdminUser();
  3485. $completionexpected = time();
  3486. $duedate = time();
  3487. $course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
  3488. $course2 = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
  3489. $assign = $this->getDataGenerator()->create_module('assign', [
  3490. 'course' => $course,
  3491. 'completionexpected' => $completionexpected,
  3492. 'duedate' => $duedate
  3493. ]);
  3494. // No assign instances in this course.
  3495. $this->assertFalse(course_module_bulk_update_calendar_events('assign', $course2->id));
  3496. // No book instances for the site.
  3497. $this->assertFalse(course_module_bulk_update_calendar_events('book'));
  3498. // Update all assign instances.
  3499. $this->assertTrue(course_module_bulk_update_calendar_events('assign'));
  3500. // Update the assign instances for this course.
  3501. $this->assertTrue(course_module_bulk_update_calendar_events('assign', $course->id));
  3502. }
  3503. /**
  3504. * Test that a student can view participants in a course they are enrolled in.
  3505. */
  3506. public function test_course_can_view_participants_as_student() {
  3507. $this->resetAfterTest();
  3508. $course = $this->getDataGenerator()->create_course();
  3509. $coursecontext = context_course::instance($course->id);
  3510. $user = $this->getDataGenerator()->create_user();
  3511. $this->getDataGenerator()->enrol_user($user->id, $course->id);
  3512. $this->setUser($user);
  3513. $this->assertTrue(course_can_view_participants($coursecontext));
  3514. }
  3515. /**
  3516. * Test that a student in a course can not view participants on the site.
  3517. */
  3518. public function test_course_can_view_participants_as_student_on_site() {
  3519. $this->resetAfterTest();
  3520. $course = $this->getDataGenerator()->create_course();
  3521. $user = $this->getDataGenerator()->create_user();
  3522. $this->getDataGenerator()->enrol_user($user->id, $course->id);
  3523. $this->setUser($user);
  3524. $this->assertFalse(course_can_view_participants(context_system::instance()));
  3525. }
  3526. /**
  3527. * Test that an admin can view participants on the site.
  3528. */
  3529. public function test_course_can_view_participants_as_admin_on_site() {
  3530. $this->resetAfterTest();
  3531. $this->setAdminUser();
  3532. $this->assertTrue(course_can_view_participants(context_system::instance()));
  3533. }
  3534. /**
  3535. * Test teachers can view participants in a course they are enrolled in.
  3536. */
  3537. public function test_course_can_view_participants_as_teacher() {
  3538. global $DB;
  3539. $this->resetAfterTest();
  3540. $course = $this->getDataGenerator()->create_course();
  3541. $coursecontext = context_course::instance($course->id);
  3542. $user = $this->getDataGenerator()->create_user();
  3543. $roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
  3544. $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
  3545. $this->setUser($user);
  3546. $this->assertTrue(course_can_view_participants($coursecontext));
  3547. }
  3548. /**
  3549. * Check the teacher can still view the participants page without the 'viewparticipants' cap.
  3550. */
  3551. public function test_course_can_view_participants_as_teacher_without_view_participants_cap() {
  3552. global $DB;
  3553. $this->resetAfterTest();
  3554. $course = $this->getDataGenerator()->create_course();
  3555. $coursecontext = context_course::instance($course->id);
  3556. $user = $this->getDataGenerator()->create_user();
  3557. $roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
  3558. $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
  3559. $this->setUser($user);
  3560. // Disable one of the capabilties.
  3561. assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $coursecontext);
  3562. // Should still be able to view the page as they have the 'moodle/course:enrolreview' cap.
  3563. $this->assertTrue(course_can_view_participants($coursecontext));
  3564. }
  3565. /**
  3566. * Check the teacher can still view the participants page without the 'moodle/course:enrolreview' cap.
  3567. */
  3568. public function test_course_can_view_participants_as_teacher_without_enrol_review_cap() {
  3569. global $DB;
  3570. $this->resetAfterTest();
  3571. $course = $this->getDataGenerator()->create_course();
  3572. $coursecontext = context_course::instance($course->id);
  3573. $user = $this->getDataGenerator()->create_user();
  3574. $roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
  3575. $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
  3576. $this->setUser($user);
  3577. // Disable one of the capabilties.
  3578. assign_capability('moodle/course:enrolreview', CAP_PROHIBIT, $roleid, $coursecontext);
  3579. // Should still be able to view the page as they have the 'moodle/course:viewparticipants' cap.
  3580. $this->assertTrue(course_can_view_participants($coursecontext));
  3581. }
  3582. /**
  3583. * Check the teacher can not view the participants page without the required caps.
  3584. */
  3585. public function test_course_can_view_participants_as_teacher_without_required_caps() {
  3586. global $DB;
  3587. $this->resetAfterTest();
  3588. $course = $this->getDataGenerator()->create_course();
  3589. $coursecontext = context_course::instance($course->id);
  3590. $user = $this->getDataGenerator()->create_user();
  3591. $roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
  3592. $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
  3593. $this->setUser($user);
  3594. // Disable the capabilities.
  3595. assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $coursecontext);
  3596. assign_capability('moodle/course:enrolreview', CAP_PROHIBIT, $roleid, $coursecontext);
  3597. $this->assertFalse(course_can_view_participants($coursecontext));
  3598. }
  3599. /**
  3600. * Check that an exception is not thrown if we can view the participants page.
  3601. */
  3602. public function test_course_require_view_participants() {
  3603. $this->resetAfterTest();
  3604. $course = $this->getDataGenerator()->create_course();
  3605. $coursecontext = context_course::instance($course->id);
  3606. $user = $this->getDataGenerator()->create_user();
  3607. $this->getDataGenerator()->enrol_user($user->id, $course->id);
  3608. $this->setUser($user);
  3609. course_require_view_participants($coursecontext);
  3610. }
  3611. /**
  3612. * Check that an exception is thrown if we can't view the participants page.
  3613. */
  3614. public function test_course_require_view_participants_as_student_on_site() {
  3615. $this->resetAfterTest();
  3616. $course = $this->getDataGenerator()->create_course();
  3617. $user = $this->getDataGenerator()->create_user();
  3618. $this->getDataGenerator()->enrol_user($user->id, $course->id);
  3619. $this->setUser($user);
  3620. $this->expectException('required_capability_exception');
  3621. course_require_view_participants(context_system::instance());
  3622. }
  3623. /**
  3624. * Testing the can_download_from_backup_filearea fn.
  3625. */
  3626. public function test_can_download_from_backup_filearea() {
  3627. global $DB;
  3628. $this->resetAfterTest();
  3629. $course = $this->getDataGenerator()->create_course();
  3630. $context = context_course::instance($course->id);
  3631. $user = $this->getDataGenerator()->create_user();
  3632. $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
  3633. $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
  3634. // The 'automated' backup area. Downloading from this area requires two capabilities.
  3635. // If the user has only the 'backup:downloadfile' capability.
  3636. unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context);
  3637. assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
  3638. $this->assertFalse(can_download_from_backup_filearea('automated', $context, $user));
  3639. // If the user has only the 'restore:userinfo' capability.
  3640. unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context);
  3641. assign_capability('moodle/restore:userinfo', CAP_ALLOW, $teacherrole->id, $context);
  3642. $this->assertFalse(can_download_from_backup_filearea('automated', $context, $user));
  3643. // If the user has both capabilities.
  3644. assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
  3645. assign_capability('moodle/restore:userinfo', CAP_ALLOW, $teacherrole->id, $context);
  3646. $this->assertTrue(can_download_from_backup_filearea('automated', $context, $user));
  3647. // Is the user has neither of the capabilities.
  3648. unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context);
  3649. unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context);
  3650. $this->assertFalse(can_download_from_backup_filearea('automated', $context, $user));
  3651. // The 'course ' and 'backup' backup file areas. These are governed by the same download capability.
  3652. // User has the capability.
  3653. unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context);
  3654. assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
  3655. $this->assertTrue(can_download_from_backup_filearea('course', $context, $user));
  3656. $this->assertTrue(can_download_from_backup_filearea('backup', $context, $user));
  3657. // User doesn't have the capability.
  3658. unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context);
  3659. $this->assertFalse(can_download_from_backup_filearea('course', $context, $user));
  3660. $this->assertFalse(can_download_from_backup_filearea('backup', $context, $user));
  3661. // A file area that doesn't exist. No permissions, regardless of capabilities.
  3662. assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
  3663. $this->assertFalse(can_download_from_backup_filearea('testing', $context, $user));
  3664. }
  3665. /**
  3666. * Test cases for the course_classify_courses_for_timeline test.
  3667. */
  3668. public function get_course_classify_courses_for_timeline_test_cases() {
  3669. $now = time();
  3670. $day = 86400;
  3671. return [
  3672. 'no courses' => [
  3673. 'coursesdata' => [],
  3674. 'expected' => [
  3675. COURSE_TIMELINE_PAST => [],
  3676. COURSE_TIMELINE_FUTURE => [],
  3677. COURSE_TIMELINE_INPROGRESS => []
  3678. ]
  3679. ],
  3680. 'only past' => [
  3681. 'coursesdata' => [
  3682. [
  3683. 'shortname' => 'past1',
  3684. 'startdate' => $now - ($day * 2),
  3685. 'enddate' => $now - $day
  3686. ],
  3687. [
  3688. 'shortname' => 'past2',
  3689. 'startdate' => $now - ($day * 2),
  3690. 'enddate' => $now - $day
  3691. ]
  3692. ],
  3693. 'expected' => [
  3694. COURSE_TIMELINE_PAST => ['past1', 'past2'],
  3695. COURSE_TIMELINE_FUTURE => [],
  3696. COURSE_TIMELINE_INPROGRESS => []
  3697. ]
  3698. ],
  3699. 'only in progress' => [
  3700. 'coursesdata' => [
  3701. [
  3702. 'shortname' => 'inprogress1',
  3703. 'startdate' => $now - $day,
  3704. 'enddate' => $now + $day
  3705. ],
  3706. [
  3707. 'shortname' => 'inprogress2',
  3708. 'startdate' => $now - $day,
  3709. 'enddate' => $now + $day
  3710. ]
  3711. ],
  3712. 'expected' => [
  3713. COURSE_TIMELINE_PAST => [],
  3714. COURSE_TIMELINE_FUTURE => [],
  3715. COURSE_TIMELINE_INPROGRESS => ['inprogress1', 'inprogress2']
  3716. ]
  3717. ],
  3718. 'only future' => [
  3719. 'coursesdata' => [
  3720. [
  3721. 'shortname' => 'future1',
  3722. 'startdate' => $now + $day
  3723. ],
  3724. [
  3725. 'shortname' => 'future2',
  3726. 'startdate' => $now + $day
  3727. ]
  3728. ],
  3729. 'expected' => [
  3730. COURSE_TIMELINE_PAST => [],
  3731. COURSE_TIMELINE_FUTURE => ['future1', 'future2'],
  3732. COURSE_TIMELINE_INPROGRESS => []
  3733. ]
  3734. ],
  3735. 'combination' => [
  3736. 'coursesdata' => [
  3737. [
  3738. 'shortname' => 'past1',
  3739. 'startdate' => $now - ($day * 2),
  3740. 'enddate' => $now - $day
  3741. ],
  3742. [
  3743. 'shortname' => 'past2',
  3744. 'startdate' => $now - ($day * 2),
  3745. 'enddate' => $now - $day
  3746. ],
  3747. [
  3748. 'shortname' => 'inprogress1',
  3749. 'startdate' => $now - $day,
  3750. 'enddate' => $now + $day
  3751. ],
  3752. [
  3753. 'shortname' => 'inprogress2',
  3754. 'startdate' => $now - $day,
  3755. 'enddate' => $now + $day
  3756. ],
  3757. [
  3758. 'shortname' => 'future1',
  3759. 'startdate' => $now + $day
  3760. ],
  3761. [
  3762. 'shortname' => 'future2',
  3763. 'startdate' => $now + $day
  3764. ]
  3765. ],
  3766. 'expected' => [
  3767. COURSE_TIMELINE_PAST => ['past1', 'past2'],
  3768. COURSE_TIMELINE_FUTURE => ['future1', 'future2'],
  3769. COURSE_TIMELINE_INPROGRESS => ['inprogress1', 'inprogress2']
  3770. ]
  3771. ],
  3772. ];
  3773. }
  3774. /**
  3775. * Test the course_classify_courses_for_timeline function.
  3776. *
  3777. * @dataProvider get_course_classify_courses_for_timeline_test_cases()
  3778. * @param array $coursesdata Courses to create
  3779. * @param array $expected Expected test results.
  3780. */
  3781. public function test_course_classify_courses_for_timeline($coursesdata, $expected) {
  3782. $this->resetAfterTest();
  3783. $generator = $this->getDataGenerator();
  3784. $courses = array_map(function($coursedata) use ($generator) {
  3785. return $generator->create_course($coursedata);
  3786. }, $coursesdata);
  3787. sort($expected[COURSE_TIMELINE_PAST]);
  3788. sort($expected[COURSE_TIMELINE_FUTURE]);
  3789. sort($expected[COURSE_TIMELINE_INPROGRESS]);
  3790. $results = course_classify_courses_for_timeline($courses);
  3791. $actualpast = array_map(function($result) {
  3792. return $result->shortname;
  3793. }, $results[COURSE_TIMELINE_PAST]);
  3794. $actualfuture = array_map(function($result) {
  3795. return $result->shortname;
  3796. }, $results[COURSE_TIMELINE_FUTURE]);
  3797. $actualinprogress = array_map(function($result) {
  3798. return $result->shortname;
  3799. }, $results[COURSE_TIMELINE_INPROGRESS]);
  3800. sort($actualpast);
  3801. sort($actualfuture);
  3802. sort($actualinprogress);
  3803. $this->assertEquals($expected[COURSE_TIMELINE_PAST], $actualpast);
  3804. $this->assertEquals($expected[COURSE_TIMELINE_FUTURE], $actualfuture);
  3805. $this->assertEquals($expected[COURSE_TIMELINE_INPROGRESS], $actualinprogress);
  3806. }
  3807. /**
  3808. * Test cases for the course_get_enrolled_courses_for_logged_in_user tests.
  3809. */
  3810. public function get_course_get_enrolled_courses_for_logged_in_user_test_cases() {
  3811. $buildexpectedresult = function($limit, $offset) {
  3812. $result = [];
  3813. for ($i = $offset; $i < $offset + $limit; $i++) {
  3814. $result[] = "testcourse{$i}";
  3815. }
  3816. return $result;
  3817. };
  3818. return [
  3819. 'zero records' => [
  3820. 'dbquerylimit' => 3,
  3821. 'totalcourses' => 0,
  3822. 'limit' => 0,
  3823. 'offset' => 0,
  3824. 'expecteddbqueries' => 4,
  3825. 'expectedresult' => $buildexpectedresult(0, 0)
  3826. ],
  3827. 'less than query limit' => [
  3828. 'dbquerylimit' => 3,
  3829. 'totalcourses' => 2,
  3830. 'limit' => 0,
  3831. 'offset' => 0,
  3832. 'expecteddbqueries' => 2,
  3833. 'expectedresult' => $buildexpectedresult(2, 0)
  3834. ],
  3835. 'more than query limit' => [
  3836. 'dbquerylimit' => 3,
  3837. 'totalcourses' => 7,
  3838. 'limit' => 0,
  3839. 'offset' => 0,
  3840. 'expecteddbqueries' => 4,
  3841. 'expectedresult' => $buildexpectedresult(7, 0)
  3842. ],
  3843. 'limit less than query limit' => [
  3844. 'dbquerylimit' => 3,
  3845. 'totalcourses' => 7,
  3846. 'limit' => 2,
  3847. 'offset' => 0,
  3848. 'expecteddbqueries' => 2,
  3849. 'expectedresult' => $buildexpectedresult(2, 0)
  3850. ],
  3851. 'limit less than query limit with offset' => [
  3852. 'dbquerylimit' => 3,
  3853. 'totalcourses' => 7,
  3854. 'limit' => 2,
  3855. 'offset' => 2,
  3856. 'expecteddbqueries' => 2,
  3857. 'expectedresult' => $buildexpectedresult(2, 2)
  3858. ],
  3859. 'limit less than total' => [
  3860. 'dbquerylimit' => 3,
  3861. 'totalcourses' => 9,
  3862. 'limit' => 6,
  3863. 'offset' => 0,
  3864. 'expecteddbqueries' => 3,
  3865. 'expectedresult' => $buildexpectedresult(6, 0)
  3866. ],
  3867. 'less results than limit' => [
  3868. 'dbquerylimit' => 4,
  3869. 'totalcourses' => 9,
  3870. 'limit' => 20,
  3871. 'offset' => 0,
  3872. 'expecteddbqueries' => 4,
  3873. 'expectedresult' => $buildexpectedresult(9, 0)
  3874. ],
  3875. 'less results than limit exact divisible' => [
  3876. 'dbquerylimit' => 3,
  3877. 'totalcourses' => 9,
  3878. 'limit' => 20,
  3879. 'offset' => 0,
  3880. 'expecteddbqueries' => 5,
  3881. 'expectedresult' => $buildexpectedresult(9, 0)
  3882. ],
  3883. 'less results than limit with offset' => [
  3884. 'dbquerylimit' => 3,
  3885. 'totalcourses' => 9,
  3886. 'limit' => 10,
  3887. 'offset' => 5,
  3888. 'expecteddbqueries' => 3,
  3889. 'expectedresult' => $buildexpectedresult(4, 5)
  3890. ],
  3891. ];
  3892. }
  3893. /**
  3894. * Test the course_get_enrolled_courses_for_logged_in_user function.
  3895. *
  3896. * @dataProvider get_course_get_enrolled_courses_for_logged_in_user_test_cases()
  3897. * @param int $dbquerylimit Number of records to load per DB request
  3898. * @param int $totalcourses Number of courses to create
  3899. * @param int $limit Maximum number of results to get.
  3900. * @param int $offset Skip this number of results from the start of the result set.
  3901. * @param int $expecteddbqueries The number of DB queries expected during the test.
  3902. * @param array $expectedresult Expected test results.
  3903. */
  3904. public function test_course_get_enrolled_courses_for_logged_in_user(
  3905. $dbquerylimit,
  3906. $totalcourses,
  3907. $limit,
  3908. $offset,
  3909. $expecteddbqueries,
  3910. $expectedresult
  3911. ) {
  3912. global $DB;
  3913. $this->resetAfterTest();
  3914. $generator = $this->getDataGenerator();
  3915. $student = $generator->create_user();
  3916. for ($i = 0; $i < $totalcourses; $i++) {
  3917. $shortname = "testcourse{$i}";
  3918. $course = $generator->create_course(['shortname' => $shortname]);
  3919. $generator->enrol_user($student->id, $course->id, 'student');
  3920. }
  3921. $this->setUser($student);
  3922. $initialquerycount = $DB->perf_get_queries();
  3923. $courses = course_get_enrolled_courses_for_logged_in_user($limit, $offset, 'shortname ASC', 'shortname', $dbquerylimit);
  3924. // Loop over the result set to force the lazy loading to kick in so that we can check the
  3925. // number of DB queries.
  3926. $actualresult = array_map(function($course) {
  3927. return $course->shortname;
  3928. }, iterator_to_array($courses, false));
  3929. sort($expectedresult);
  3930. $this->assertEquals($expectedresult, $actualresult);
  3931. $this->assertLessThanOrEqual($expecteddbqueries, $DB->perf_get_queries() - $initialquerycount);
  3932. }
  3933. /**
  3934. * Test cases for the course_filter_courses_by_timeline_classification tests.
  3935. */
  3936. public function get_course_filter_courses_by_timeline_classification_test_cases() {
  3937. $now = time();
  3938. $day = 86400;
  3939. $coursedata = [
  3940. [
  3941. 'shortname' => 'apast',
  3942. 'startdate' => $now - ($day * 2),
  3943. 'enddate' => $now - $day
  3944. ],
  3945. [
  3946. 'shortname' => 'bpast',
  3947. 'startdate' => $now - ($day * 2),
  3948. 'enddate' => $now - $day
  3949. ],
  3950. [
  3951. 'shortname' => 'cpast',
  3952. 'startdate' => $now - ($day * 2),
  3953. 'enddate' => $now - $day
  3954. ],
  3955. [
  3956. 'shortname' => 'dpast',
  3957. 'startdate' => $now - ($day * 2),
  3958. 'enddate' => $now - $day
  3959. ],
  3960. [
  3961. 'shortname' => 'epast',
  3962. 'startdate' => $now - ($day * 2),
  3963. 'enddate' => $now - $day
  3964. ],
  3965. [
  3966. 'shortname' => 'ainprogress',
  3967. 'startdate' => $now - $day,
  3968. 'enddate' => $now + $day
  3969. ],
  3970. [
  3971. 'shortname' => 'binprogress',
  3972. 'startdate' => $now - $day,
  3973. 'enddate' => $now + $day
  3974. ],
  3975. [
  3976. 'shortname' => 'cinprogress',
  3977. 'startdate' => $now - $day,
  3978. 'enddate' => $now + $day
  3979. ],
  3980. [
  3981. 'shortname' => 'dinprogress',
  3982. 'startdate' => $now - $day,
  3983. 'enddate' => $now + $day
  3984. ],
  3985. [
  3986. 'shortname' => 'einprogress',
  3987. 'startdate' => $now - $day,
  3988. 'enddate' => $now + $day
  3989. ],
  3990. [
  3991. 'shortname' => 'afuture',
  3992. 'startdate' => $now + $day
  3993. ],
  3994. [
  3995. 'shortname' => 'bfuture',
  3996. 'startdate' => $now + $day
  3997. ],
  3998. [
  3999. 'shortname' => 'cfuture',
  4000. 'startdate' => $now + $day
  4001. ],
  4002. [
  4003. 'shortname' => 'dfuture',
  4004. 'startdate' => $now + $day
  4005. ],
  4006. [
  4007. 'shortname' => 'efuture',
  4008. 'startdate' => $now + $day
  4009. ]
  4010. ];
  4011. // Raw enrolled courses result set should be returned in this order:
  4012. // afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast,
  4013. // dfuture, dinprogress, dpast, efuture, einprogress, epast
  4014. //
  4015. // By classification the offset values for each record should be:
  4016. // COURSE_TIMELINE_FUTURE
  4017. // 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture)
  4018. // COURSE_TIMELINE_INPROGRESS
  4019. // 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress)
  4020. // COURSE_TIMELINE_PAST
  4021. // 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast).
  4022. return [
  4023. 'empty set' => [
  4024. 'coursedata' => [],
  4025. 'classification' => COURSE_TIMELINE_FUTURE,
  4026. 'limit' => 2,
  4027. 'offset' => 0,
  4028. 'expectedcourses' => [],
  4029. 'expectedprocessedcount' => 0
  4030. ],
  4031. // COURSE_TIMELINE_FUTURE.
  4032. 'future not limit no offset' => [
  4033. 'coursedata' => $coursedata,
  4034. 'classification' => COURSE_TIMELINE_FUTURE,
  4035. 'limit' => 0,
  4036. 'offset' => 0,
  4037. 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
  4038. 'expectedprocessedcount' => 15
  4039. ],
  4040. 'future no offset' => [
  4041. 'coursedata' => $coursedata,
  4042. 'classification' => COURSE_TIMELINE_FUTURE,
  4043. 'limit' => 2,
  4044. 'offset' => 0,
  4045. 'expectedcourses' => ['afuture', 'bfuture'],
  4046. 'expectedprocessedcount' => 4
  4047. ],
  4048. 'future offset' => [
  4049. 'coursedata' => $coursedata,
  4050. 'classification' => COURSE_TIMELINE_FUTURE,
  4051. 'limit' => 2,
  4052. 'offset' => 2,
  4053. 'expectedcourses' => ['bfuture', 'cfuture'],
  4054. 'expectedprocessedcount' => 5
  4055. ],
  4056. 'future exact limit' => [
  4057. 'coursedata' => $coursedata,
  4058. 'classification' => COURSE_TIMELINE_FUTURE,
  4059. 'limit' => 5,
  4060. 'offset' => 0,
  4061. 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
  4062. 'expectedprocessedcount' => 13
  4063. ],
  4064. 'future limit less results' => [
  4065. 'coursedata' => $coursedata,
  4066. 'classification' => COURSE_TIMELINE_FUTURE,
  4067. 'limit' => 10,
  4068. 'offset' => 0,
  4069. 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
  4070. 'expectedprocessedcount' => 15
  4071. ],
  4072. 'future limit less results with offset' => [
  4073. 'coursedata' => $coursedata,
  4074. 'classification' => COURSE_TIMELINE_FUTURE,
  4075. 'limit' => 10,
  4076. 'offset' => 5,
  4077. 'expectedcourses' => ['cfuture', 'dfuture', 'efuture'],
  4078. 'expectedprocessedcount' => 10
  4079. ],
  4080. ];
  4081. }
  4082. /**
  4083. * Test the course_get_enrolled_courses_for_logged_in_user_from_search function.
  4084. */
  4085. public function test_course_get_enrolled_courses_for_logged_in_user_from_search() {
  4086. global $DB;
  4087. // Set up.
  4088. $this->resetAfterTest();
  4089. $generator = $this->getDataGenerator();
  4090. $student = $generator->create_user();
  4091. $cat1 = core_course_category::create(['name' => 'Cat1']);
  4092. $cat2 = core_course_category::create(['name' => 'Cat2', 'parent' => $cat1->id]);
  4093. $c1 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Test 3', 'summary' => 'Magic', 'idnumber' => 'ID3']);
  4094. $c2 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Test 1', 'summary' => 'Magic']);
  4095. $c3 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Математика', 'summary' => ' Test Magic']);
  4096. $c4 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Test 4', 'summary' => 'Magic', 'idnumber' => 'ID4']);
  4097. $c5 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Test 5', 'summary' => 'Magic']);
  4098. $c6 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Дискретная Математика', 'summary' => 'Magic']);
  4099. $c7 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Test 7', 'summary' => 'Magic']);
  4100. $c8 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Test 8', 'summary' => 'Magic']);
  4101. for ($i = 1; $i < 9; $i++) {
  4102. $generator->enrol_user($student->id, ${"c$i"}->id, 'student');
  4103. }
  4104. $this->setUser($student);
  4105. $returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search(
  4106. 0,
  4107. 0,
  4108. 'id ASC',
  4109. null,
  4110. COURSE_DB_QUERY_LIMIT,
  4111. ['search' => 'test'],
  4112. ['idonly' => true]
  4113. );
  4114. $actualresult = array_map(function($course) {
  4115. return $course->id;
  4116. }, iterator_to_array($returnedcourses, false));
  4117. $this->assertEquals([$c1->id, $c2->id, $c3->id, $c4->id, $c5->id, $c7->id, $c8->id], $actualresult);
  4118. // Test no courses matching the search.
  4119. $returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search(
  4120. 0,
  4121. 0,
  4122. 'id ASC',
  4123. null,
  4124. COURSE_DB_QUERY_LIMIT,
  4125. ['search' => 'foobar'],
  4126. ['idonly' => true]
  4127. );
  4128. $actualresult = array_map(function($course) {
  4129. return $course->id;
  4130. }, iterator_to_array($returnedcourses, false));
  4131. $this->assertEquals([], $actualresult);
  4132. // Test returning all courses that have a mutual summary.
  4133. $returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search(
  4134. 0,
  4135. 0,
  4136. 'id ASC',
  4137. null,
  4138. COURSE_DB_QUERY_LIMIT,
  4139. ['search' => 'Magic'],
  4140. ['idonly' => true]
  4141. );
  4142. $actualresult = array_map(function($course) {
  4143. return $course->id;
  4144. }, iterator_to_array($returnedcourses, false));
  4145. $this->assertEquals([$c1->id, $c2->id, $c3->id, $c4->id, $c5->id, $c6->id, $c7->id, $c8->id], $actualresult);
  4146. // Test returning a unique course.
  4147. $returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search(
  4148. 0,
  4149. 0,
  4150. 'id ASC',
  4151. null,
  4152. COURSE_DB_QUERY_LIMIT,
  4153. ['search' => 'Дискретная'],
  4154. ['idonly' => true]
  4155. );
  4156. $actualresult = array_map(function($course) {
  4157. return $course->id;
  4158. }, iterator_to_array($returnedcourses, false));
  4159. $this->assertEquals([$c6->id], $actualresult);
  4160. }
  4161. /**
  4162. * Test the course_filter_courses_by_timeline_classification function.
  4163. *
  4164. * @dataProvider get_course_filter_courses_by_timeline_classification_test_cases()
  4165. * @param array $coursedata Course test data to create.
  4166. * @param string $classification Timeline classification.
  4167. * @param int $limit Maximum number of results to return.
  4168. * @param int $offset Results to skip at the start of the result set.
  4169. * @param string[] $expectedcourses Expected courses in results.
  4170. * @param int $expectedprocessedcount Expected number of course records to be processed.
  4171. */
  4172. public function test_course_filter_courses_by_timeline_classification(
  4173. $coursedata,
  4174. $classification,
  4175. $limit,
  4176. $offset,
  4177. $expectedcourses,
  4178. $expectedprocessedcount
  4179. ) {
  4180. $this->resetAfterTest();
  4181. $generator = $this->getDataGenerator();
  4182. $courses = array_map(function($coursedata) use ($generator) {
  4183. return $generator->create_course($coursedata);
  4184. }, $coursedata);
  4185. $student = $generator->create_user();
  4186. foreach ($courses as $course) {
  4187. $generator->enrol_user($student->id, $course->id, 'student');
  4188. }
  4189. $this->setUser($student);
  4190. $coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname');
  4191. list($result, $processedcount) = course_filter_courses_by_timeline_classification(
  4192. $coursesgenerator,
  4193. $classification,
  4194. $limit
  4195. );
  4196. $actual = array_map(function($course) {
  4197. return $course->shortname;
  4198. }, $result);
  4199. $this->assertEquals($expectedcourses, $actual);
  4200. $this->assertEquals($expectedprocessedcount, $processedcount);
  4201. }
  4202. /**
  4203. * Test cases for the course_filter_courses_by_timeline_classification tests.
  4204. */
  4205. public function get_course_filter_courses_by_customfield_test_cases() {
  4206. global $CFG;
  4207. require_once($CFG->dirroot.'/blocks/myoverview/lib.php');
  4208. $coursedata = [
  4209. [
  4210. 'shortname' => 'C1',
  4211. 'customfield_checkboxfield' => 1,
  4212. 'customfield_datefield' => strtotime('2001-02-01T12:00:00Z'),
  4213. 'customfield_selectfield' => 1,
  4214. 'customfield_textfield' => 'fish',
  4215. ],
  4216. [
  4217. 'shortname' => 'C2',
  4218. 'customfield_checkboxfield' => 0,
  4219. 'customfield_datefield' => strtotime('1980-08-05T13:00:00Z'),
  4220. ],
  4221. [
  4222. 'shortname' => 'C3',
  4223. 'customfield_checkboxfield' => 0,
  4224. 'customfield_datefield' => strtotime('2001-02-01T12:00:00Z'),
  4225. 'customfield_selectfield' => 2,
  4226. 'customfield_textfield' => 'dog',
  4227. ],
  4228. [
  4229. 'shortname' => 'C4',
  4230. 'customfield_checkboxfield' => 1,
  4231. 'customfield_selectfield' => 3,
  4232. 'customfield_textfield' => 'cat',
  4233. ],
  4234. [
  4235. 'shortname' => 'C5',
  4236. 'customfield_datefield' => strtotime('1980-08-06T13:00:00Z'),
  4237. 'customfield_selectfield' => 2,
  4238. 'customfield_textfield' => 'fish',
  4239. ],
  4240. ];
  4241. return [
  4242. 'empty set' => [
  4243. 'coursedata' => [],
  4244. 'customfield' => 'checkboxfield',
  4245. 'customfieldvalue' => 1,
  4246. 'limit' => 10,
  4247. 'offset' => 0,
  4248. 'expectedcourses' => [],
  4249. 'expectedprocessedcount' => 0
  4250. ],
  4251. 'checkbox yes' => [
  4252. 'coursedata' => $coursedata,
  4253. 'customfield' => 'checkboxfield',
  4254. 'customfieldvalue' => 1,
  4255. 'limit' => 10,
  4256. 'offset' => 0,
  4257. 'expectedcourses' => ['C1', 'C4'],
  4258. 'expectedprocessedcount' => 5
  4259. ],
  4260. 'checkbox no' => [
  4261. 'coursedata' => $coursedata,
  4262. 'customfield' => 'checkboxfield',
  4263. 'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
  4264. 'limit' => 10,
  4265. 'offset' => 0,
  4266. 'expectedcourses' => ['C2', 'C3', 'C5'],
  4267. 'expectedprocessedcount' => 5
  4268. ],
  4269. 'date 1 Feb 2001' => [
  4270. 'coursedata' => $coursedata,
  4271. 'customfield' => 'datefield',
  4272. 'customfieldvalue' => strtotime('2001-02-01T12:00:00Z'),
  4273. 'limit' => 10,
  4274. 'offset' => 0,
  4275. 'expectedcourses' => ['C1', 'C3'],
  4276. 'expectedprocessedcount' => 5
  4277. ],
  4278. 'date 6 Aug 1980' => [
  4279. 'coursedata' => $coursedata,
  4280. 'customfield' => 'datefield',
  4281. 'customfieldvalue' => strtotime('1980-08-06T13:00:00Z'),
  4282. 'limit' => 10,
  4283. 'offset' => 0,
  4284. 'expectedcourses' => ['C5'],
  4285. 'expectedprocessedcount' => 5
  4286. ],
  4287. 'date no date' => [
  4288. 'coursedata' => $coursedata,
  4289. 'customfield' => 'datefield',
  4290. 'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
  4291. 'limit' => 10,
  4292. 'offset' => 0,
  4293. 'expectedcourses' => ['C4'],
  4294. 'expectedprocessedcount' => 5
  4295. ],
  4296. 'select Option 1' => [
  4297. 'coursedata' => $coursedata,
  4298. 'customfield' => 'selectfield',
  4299. 'customfieldvalue' => 1,
  4300. 'limit' => 10,
  4301. 'offset' => 0,
  4302. 'expectedcourses' => ['C1'],
  4303. 'expectedprocessedcount' => 5
  4304. ],
  4305. 'select Option 2' => [
  4306. 'coursedata' => $coursedata,
  4307. 'customfield' => 'selectfield',
  4308. 'customfieldvalue' => 2,
  4309. 'limit' => 10,
  4310. 'offset' => 0,
  4311. 'expectedcourses' => ['C3', 'C5'],
  4312. 'expectedprocessedcount' => 5
  4313. ],
  4314. 'select no select' => [
  4315. 'coursedata' => $coursedata,
  4316. 'customfield' => 'selectfield',
  4317. 'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
  4318. 'limit' => 10,
  4319. 'offset' => 0,
  4320. 'expectedcourses' => ['C2'],
  4321. 'expectedprocessedcount' => 5
  4322. ],
  4323. 'text fish' => [
  4324. 'coursedata' => $coursedata,
  4325. 'customfield' => 'textfield',
  4326. 'customfieldvalue' => 'fish',
  4327. 'limit' => 10,
  4328. 'offset' => 0,
  4329. 'expectedcourses' => ['C1', 'C5'],
  4330. 'expectedprocessedcount' => 5
  4331. ],
  4332. 'text dog' => [
  4333. 'coursedata' => $coursedata,
  4334. 'customfield' => 'textfield',
  4335. 'customfieldvalue' => 'dog',
  4336. 'limit' => 10,
  4337. 'offset' => 0,
  4338. 'expectedcourses' => ['C3'],
  4339. 'expectedprocessedcount' => 5
  4340. ],
  4341. 'text no text' => [
  4342. 'coursedata' => $coursedata,
  4343. 'customfield' => 'textfield',
  4344. 'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
  4345. 'limit' => 10,
  4346. 'offset' => 0,
  4347. 'expectedcourses' => ['C2'],
  4348. 'expectedprocessedcount' => 5
  4349. ],
  4350. 'checkbox limit no' => [
  4351. 'coursedata' => $coursedata,
  4352. 'customfield' => 'checkboxfield',
  4353. 'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
  4354. 'limit' => 2,
  4355. 'offset' => 0,
  4356. 'expectedcourses' => ['C2', 'C3'],
  4357. 'expectedprocessedcount' => 3
  4358. ],
  4359. 'checkbox limit offset no' => [
  4360. 'coursedata' => $coursedata,
  4361. 'customfield' => 'checkboxfield',
  4362. 'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
  4363. 'limit' => 2,
  4364. 'offset' => 3,
  4365. 'expectedcourses' => ['C5'],
  4366. 'expectedprocessedcount' => 2
  4367. ],
  4368. ];
  4369. }
  4370. /**
  4371. * Test the course_filter_courses_by_customfield function.
  4372. *
  4373. * @dataProvider get_course_filter_courses_by_customfield_test_cases()
  4374. * @param array $coursedata Course test data to create.
  4375. * @param string $customfield Shortname of the customfield.
  4376. * @param string $customfieldvalue the value to filter by.
  4377. * @param int $limit Maximum number of results to return.
  4378. * @param int $offset Results to skip at the start of the result set.
  4379. * @param string[] $expectedcourses Expected courses in results.
  4380. * @param int $expectedprocessedcount Expected number of course records to be processed.
  4381. */
  4382. public function test_course_filter_courses_by_customfield(
  4383. $coursedata,
  4384. $customfield,
  4385. $customfieldvalue,
  4386. $limit,
  4387. $offset,
  4388. $expectedcourses,
  4389. $expectedprocessedcount
  4390. ) {
  4391. $this->resetAfterTest();
  4392. $generator = $this->getDataGenerator();
  4393. // Create the custom fields.
  4394. $generator->create_custom_field_category([
  4395. 'name' => 'Course fields',
  4396. 'component' => 'core_course',
  4397. 'area' => 'course',
  4398. 'itemid' => 0,
  4399. ]);
  4400. $generator->create_custom_field([
  4401. 'name' => 'Checkbox field',
  4402. 'category' => 'Course fields',
  4403. 'type' => 'checkbox',
  4404. 'shortname' => 'checkboxfield',
  4405. ]);
  4406. $generator->create_custom_field([
  4407. 'name' => 'Date field',
  4408. 'category' => 'Course fields',
  4409. 'type' => 'date',
  4410. 'shortname' => 'datefield',
  4411. 'configdata' => '{"mindate":0, "maxdate":0}',
  4412. ]);
  4413. $generator->create_custom_field([
  4414. 'name' => 'Select field',
  4415. 'category' => 'Course fields',
  4416. 'type' => 'select',
  4417. 'shortname' => 'selectfield',
  4418. 'configdata' => '{"options":"Option 1\nOption 2\nOption 3\nOption 4"}',
  4419. ]);
  4420. $generator->create_custom_field([
  4421. 'name' => 'Text field',
  4422. 'category' => 'Course fields',
  4423. 'type' => 'text',
  4424. 'shortname' => 'textfield',
  4425. ]);
  4426. $courses = array_map(function($coursedata) use ($generator) {
  4427. return $generator->create_course($coursedata);
  4428. }, $coursedata);
  4429. $student = $generator->create_user();
  4430. foreach ($courses as $course) {
  4431. $generator->enrol_user($student->id, $course->id, 'student');
  4432. }
  4433. $this->setUser($student);
  4434. $coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname');
  4435. list($result, $processedcount) = course_filter_courses_by_customfield(
  4436. $coursesgenerator,
  4437. $customfield,
  4438. $customfieldvalue,
  4439. $limit
  4440. );
  4441. $actual = array_map(function($course) {
  4442. return $course->shortname;
  4443. }, $result);
  4444. $this->assertEquals($expectedcourses, $actual);
  4445. $this->assertEquals($expectedprocessedcount, $processedcount);
  4446. }
  4447. /**
  4448. * Test cases for the course_filter_courses_by_timeline_classification w/ hidden courses tests.
  4449. */
  4450. public function get_course_filter_courses_by_timeline_classification_hidden_courses_test_cases() {
  4451. $now = time();
  4452. $day = 86400;
  4453. $coursedata = [
  4454. [
  4455. 'shortname' => 'apast',
  4456. 'startdate' => $now - ($day * 2),
  4457. 'enddate' => $now - $day
  4458. ],
  4459. [
  4460. 'shortname' => 'bpast',
  4461. 'startdate' => $now - ($day * 2),
  4462. 'enddate' => $now - $day
  4463. ],
  4464. [
  4465. 'shortname' => 'cpast',
  4466. 'startdate' => $now - ($day * 2),
  4467. 'enddate' => $now - $day
  4468. ],
  4469. [
  4470. 'shortname' => 'dpast',
  4471. 'startdate' => $now - ($day * 2),
  4472. 'enddate' => $now - $day
  4473. ],
  4474. [
  4475. 'shortname' => 'epast',
  4476. 'startdate' => $now - ($day * 2),
  4477. 'enddate' => $now - $day
  4478. ],
  4479. [
  4480. 'shortname' => 'ainprogress',
  4481. 'startdate' => $now - $day,
  4482. 'enddate' => $now + $day
  4483. ],
  4484. [
  4485. 'shortname' => 'binprogress',
  4486. 'startdate' => $now - $day,
  4487. 'enddate' => $now + $day
  4488. ],
  4489. [
  4490. 'shortname' => 'cinprogress',
  4491. 'startdate' => $now - $day,
  4492. 'enddate' => $now + $day
  4493. ],
  4494. [
  4495. 'shortname' => 'dinprogress',
  4496. 'startdate' => $now - $day,
  4497. 'enddate' => $now + $day
  4498. ],
  4499. [
  4500. 'shortname' => 'einprogress',
  4501. 'startdate' => $now - $day,
  4502. 'enddate' => $now + $day
  4503. ],
  4504. [
  4505. 'shortname' => 'afuture',
  4506. 'startdate' => $now + $day
  4507. ],
  4508. [
  4509. 'shortname' => 'bfuture',
  4510. 'startdate' => $now + $day
  4511. ],
  4512. [
  4513. 'shortname' => 'cfuture',
  4514. 'startdate' => $now + $day
  4515. ],
  4516. [
  4517. 'shortname' => 'dfuture',
  4518. 'startdate' => $now + $day
  4519. ],
  4520. [
  4521. 'shortname' => 'efuture',
  4522. 'startdate' => $now + $day
  4523. ]
  4524. ];
  4525. // Raw enrolled courses result set should be returned in this order:
  4526. // afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast,
  4527. // dfuture, dinprogress, dpast, efuture, einprogress, epast
  4528. //
  4529. // By classification the offset values for each record should be:
  4530. // COURSE_TIMELINE_FUTURE
  4531. // 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture)
  4532. // COURSE_TIMELINE_INPROGRESS
  4533. // 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress)
  4534. // COURSE_TIMELINE_PAST
  4535. // 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast).
  4536. return [
  4537. 'empty set' => [
  4538. 'coursedata' => [],
  4539. 'classification' => COURSE_TIMELINE_FUTURE,
  4540. 'limit' => 2,
  4541. 'offset' => 0,
  4542. 'expectedcourses' => [],
  4543. 'expectedprocessedcount' => 0,
  4544. 'hiddencourse' => ''
  4545. ],
  4546. // COURSE_TIMELINE_FUTURE.
  4547. 'future not limit no offset' => [
  4548. 'coursedata' => $coursedata,
  4549. 'classification' => COURSE_TIMELINE_FUTURE,
  4550. 'limit' => 0,
  4551. 'offset' => 0,
  4552. 'expectedcourses' => ['afuture', 'cfuture', 'dfuture', 'efuture'],
  4553. 'expectedprocessedcount' => 15,
  4554. 'hiddencourse' => 'bfuture'
  4555. ],
  4556. 'future no offset' => [
  4557. 'coursedata' => $coursedata,
  4558. 'classification' => COURSE_TIMELINE_FUTURE,
  4559. 'limit' => 2,
  4560. 'offset' => 0,
  4561. 'expectedcourses' => ['afuture', 'cfuture'],
  4562. 'expectedprocessedcount' => 7,
  4563. 'hiddencourse' => 'bfuture'
  4564. ],
  4565. 'future offset' => [
  4566. 'coursedata' => $coursedata,
  4567. 'classification' => COURSE_TIMELINE_FUTURE,
  4568. 'limit' => 2,
  4569. 'offset' => 2,
  4570. 'expectedcourses' => ['bfuture', 'dfuture'],
  4571. 'expectedprocessedcount' => 8,
  4572. 'hiddencourse' => 'cfuture'
  4573. ],
  4574. 'future exact limit' => [
  4575. 'coursedata' => $coursedata,
  4576. 'classification' => COURSE_TIMELINE_FUTURE,
  4577. 'limit' => 5,
  4578. 'offset' => 0,
  4579. 'expectedcourses' => ['afuture', 'cfuture', 'dfuture', 'efuture'],
  4580. 'expectedprocessedcount' => 15,
  4581. 'hiddencourse' => 'bfuture'
  4582. ],
  4583. 'future limit less results' => [
  4584. 'coursedata' => $coursedata,
  4585. 'classification' => COURSE_TIMELINE_FUTURE,
  4586. 'limit' => 10,
  4587. 'offset' => 0,
  4588. 'expectedcourses' => ['afuture', 'cfuture', 'dfuture', 'efuture'],
  4589. 'expectedprocessedcount' => 15,
  4590. 'hiddencourse' => 'bfuture'
  4591. ],
  4592. 'future limit less results with offset' => [
  4593. 'coursedata' => $coursedata,
  4594. 'classification' => COURSE_TIMELINE_FUTURE,
  4595. 'limit' => 10,
  4596. 'offset' => 5,
  4597. 'expectedcourses' => ['cfuture', 'efuture'],
  4598. 'expectedprocessedcount' => 10,
  4599. 'hiddencourse' => 'dfuture'
  4600. ],
  4601. ];
  4602. }
  4603. /**
  4604. * Test the course_filter_courses_by_timeline_classification function hidden courses.
  4605. *
  4606. * @dataProvider get_course_filter_courses_by_timeline_classification_hidden_courses_test_cases()
  4607. * @param array $coursedata Course test data to create.
  4608. * @param string $classification Timeline classification.
  4609. * @param int $limit Maximum number of results to return.
  4610. * @param int $offset Results to skip at the start of the result set.
  4611. * @param string[] $expectedcourses Expected courses in results.
  4612. * @param int $expectedprocessedcount Expected number of course records to be processed.
  4613. * @param int $hiddencourse The course to hide as part of this process
  4614. */
  4615. public function test_course_filter_courses_by_timeline_classification_with_hidden_courses(
  4616. $coursedata,
  4617. $classification,
  4618. $limit,
  4619. $offset,
  4620. $expectedcourses,
  4621. $expectedprocessedcount,
  4622. $hiddencourse
  4623. ) {
  4624. $this->resetAfterTest();
  4625. $generator = $this->getDataGenerator();
  4626. $student = $generator->create_user();
  4627. $this->setUser($student);
  4628. $courses = array_map(function($coursedata) use ($generator, $hiddencourse) {
  4629. $course = $generator->create_course($coursedata);
  4630. if ($course->shortname == $hiddencourse) {
  4631. set_user_preference('block_myoverview_hidden_course_' . $course->id, true);
  4632. }
  4633. return $course;
  4634. }, $coursedata);
  4635. foreach ($courses as $course) {
  4636. $generator->enrol_user($student->id, $course->id, 'student');
  4637. }
  4638. $coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname');
  4639. list($result, $processedcount) = course_filter_courses_by_timeline_classification(
  4640. $coursesgenerator,
  4641. $classification,
  4642. $limit
  4643. );
  4644. $actual = array_map(function($course) {
  4645. return $course->shortname;
  4646. }, $result);
  4647. $this->assertEquals($expectedcourses, $actual);
  4648. $this->assertEquals($expectedprocessedcount, $processedcount);
  4649. }
  4650. /**
  4651. * Testing core_course_core_calendar_get_valid_event_timestart_range when the course has no end date.
  4652. */
  4653. public function test_core_course_core_calendar_get_valid_event_timestart_range_no_enddate() {
  4654. global $CFG;
  4655. require_once($CFG->dirroot . "/calendar/lib.php");
  4656. $this->resetAfterTest(true);
  4657. $this->setAdminUser();
  4658. $generator = $this->getDataGenerator();
  4659. $now = time();
  4660. $course = $generator->create_course(['startdate' => $now - 86400]);
  4661. // Create a course event.
  4662. $event = new \calendar_event([
  4663. 'name' => 'Test course event',
  4664. 'eventtype' => 'course',
  4665. 'courseid' => $course->id,
  4666. ]);
  4667. list ($min, $max) = core_course_core_calendar_get_valid_event_timestart_range($event, $course);
  4668. $this->assertEquals($course->startdate, $min[0]);
  4669. $this->assertNull($max);
  4670. }
  4671. /**
  4672. * Testing core_course_core_calendar_get_valid_event_timestart_range when the course has end date.
  4673. */
  4674. public function test_core_course_core_calendar_get_valid_event_timestart_range_with_enddate() {
  4675. global $CFG;
  4676. require_once($CFG->dirroot . "/calendar/lib.php");
  4677. $this->resetAfterTest(true);
  4678. $this->setAdminUser();
  4679. $generator = $this->getDataGenerator();
  4680. $now = time();
  4681. $course = $generator->create_course(['startdate' => $now - 86400, 'enddate' => $now + 86400]);
  4682. // Create a course event.
  4683. $event = new \calendar_event([
  4684. 'name' => 'Test course event',
  4685. 'eventtype' => 'course',
  4686. 'courseid' => $course->id,
  4687. ]);
  4688. list ($min, $max) = core_course_core_calendar_get_valid_event_timestart_range($event, $course);
  4689. $this->assertEquals($course->startdate, $min[0]);
  4690. $this->assertNull($max);
  4691. }
  4692. /**
  4693. * Test the course_get_recent_courses function.
  4694. */
  4695. public function test_course_get_recent_courses() {
  4696. global $DB;
  4697. $this->resetAfterTest();
  4698. $generator = $this->getDataGenerator();
  4699. $courses = array();
  4700. for ($i = 1; $i < 4; $i++) {
  4701. $courses[] = $generator->create_course();
  4702. };
  4703. $student = $generator->create_user();
  4704. foreach ($courses as $course) {
  4705. $generator->enrol_user($student->id, $course->id, 'student');
  4706. }
  4707. $this->setUser($student);
  4708. $result = course_get_recent_courses($student->id);
  4709. // No course accessed.
  4710. $this->assertCount(0, $result);
  4711. $time = time();
  4712. foreach ($courses as $course) {
  4713. $context = context_course::instance($course->id);
  4714. course_view($context);
  4715. $DB->set_field('user_lastaccess', 'timeaccess', $time, [
  4716. 'userid' => $student->id,
  4717. 'courseid' => $course->id,
  4718. ]);
  4719. $time++;
  4720. }
  4721. // Every course accessed.
  4722. $result = course_get_recent_courses($student->id);
  4723. $this->assertCount(3, $result);
  4724. // Every course accessed, result limited to 2 courses.
  4725. $result = course_get_recent_courses($student->id, 2);
  4726. $this->assertCount(2, $result);
  4727. // Every course accessed, with limit and offset should return the first course.
  4728. $result = course_get_recent_courses($student->id, 3, 2);
  4729. $this->assertCount(1, $result);
  4730. $this->assertArrayHasKey($courses[0]->id, $result);
  4731. // Every course accessed, order by shortname DESC. The last create course ($course[2]) should have the greater shortname.
  4732. $result = course_get_recent_courses($student->id, 0, 0, 'shortname DESC');
  4733. $this->assertCount(3, $result);
  4734. $this->assertEquals($courses[2]->id, array_values($result)[0]->id);
  4735. $this->assertEquals($courses[1]->id, array_values($result)[1]->id);
  4736. $this->assertEquals($courses[0]->id, array_values($result)[2]->id);
  4737. // Every course accessed, order by shortname ASC.
  4738. $result = course_get_recent_courses($student->id, 0, 0, 'shortname ASC');
  4739. $this->assertCount(3, $result);
  4740. $this->assertEquals($courses[0]->id, array_values($result)[0]->id);
  4741. $this->assertEquals($courses[1]->id, array_values($result)[1]->id);
  4742. $this->assertEquals($courses[2]->id, array_values($result)[2]->id);
  4743. $guestcourse = $generator->create_course(
  4744. (object)array('shortname' => 'guestcourse',
  4745. 'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED,
  4746. 'enrol_guest_password_0' => ''));
  4747. $context = context_course::instance($guestcourse->id);
  4748. course_view($context);
  4749. // Every course accessed, even the not enrolled one.
  4750. $result = course_get_recent_courses($student->id);
  4751. $this->assertCount(4, $result);
  4752. // Suspended student.
  4753. $this->getDataGenerator()->enrol_user($student->id, $courses[0]->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED);
  4754. // The course with suspended enrolment is not returned by the function.
  4755. $result = course_get_recent_courses($student->id);
  4756. $this->assertCount(3, $result);
  4757. $this->assertArrayNotHasKey($courses[0]->id, $result);
  4758. }
  4759. /**
  4760. * Test the validation of the sort value in course_get_recent_courses().
  4761. *
  4762. * @dataProvider course_get_recent_courses_sort_validation_provider
  4763. * @param string $sort The sort value
  4764. * @param string $expectedexceptionmsg The expected exception message
  4765. */
  4766. public function test_course_get_recent_courses_sort_validation(string $sort, string $expectedexceptionmsg) {
  4767. $this->resetAfterTest();
  4768. $user = $this->getDataGenerator()->create_user();
  4769. if (!empty($expectedexceptionmsg)) {
  4770. $this->expectException('invalid_parameter_exception');
  4771. $this->expectExceptionMessage($expectedexceptionmsg);
  4772. }
  4773. course_get_recent_courses($user->id, 0, 0, $sort);
  4774. }
  4775. /**
  4776. * Data provider for test_course_get_recent_courses_sort_validation().
  4777. *
  4778. * @return array
  4779. */
  4780. function course_get_recent_courses_sort_validation_provider() {
  4781. return [
  4782. 'Invalid sort format (SQL injection attempt)' =>
  4783. [
  4784. 'shortname DESC LIMIT 1--',
  4785. 'Invalid structure of the sort parameter, allowed structure: fieldname [ASC|DESC].',
  4786. ],
  4787. 'Sort uses \'sort by\' field that does not exist' =>
  4788. [
  4789. 'shortname DESC, xyz ASC',
  4790. 'Invalid field in the sort parameter, allowed fields: id, idnumber, summary, summaryformat, ' .
  4791. 'startdate, enddate, category, shortname, fullname, timeaccess, component, visible, ' .
  4792. 'showactivitydates, showcompletionconditions.',
  4793. ],
  4794. 'Sort uses invalid value for the sorting direction' =>
  4795. [
  4796. 'shortname xyz, lastaccess',
  4797. 'Invalid sort direction in the sort parameter, allowed values: asc, desc.',
  4798. ],
  4799. 'Valid sort format' =>
  4800. [
  4801. 'shortname asc, timeaccess',
  4802. ''
  4803. ]
  4804. ];
  4805. }
  4806. /**
  4807. * Test the course_get_recent_courses function.
  4808. */
  4809. public function test_course_get_recent_courses_with_guest() {
  4810. global $DB;
  4811. $this->resetAfterTest(true);
  4812. $student = $this->getDataGenerator()->create_user();
  4813. // Course 1 with guest access and no direct enrolment.
  4814. $course1 = $this->getDataGenerator()->create_course();
  4815. $context1 = context_course::instance($course1->id);
  4816. $record = $DB->get_record('enrol', ['courseid' => $course1->id, 'enrol' => 'guest']);
  4817. enrol_get_plugin('guest')->update_status($record, ENROL_INSTANCE_ENABLED);
  4818. // Course 2 where student is enrolled with two enrolment methods.
  4819. $course2 = $this->getDataGenerator()->create_course();
  4820. $context2 = context_course::instance($course2->id);
  4821. $record = $DB->get_record('enrol', ['courseid' => $course2->id, 'enrol' => 'self']);
  4822. enrol_get_plugin('guest')->update_status($record, ENROL_INSTANCE_ENABLED);
  4823. $this->getDataGenerator()->enrol_user($student->id, $course2->id, 'student', 'manual', 0, 0, ENROL_USER_ACTIVE);
  4824. $this->getDataGenerator()->enrol_user($student->id, $course2->id, 'student', 'self', 0, 0, ENROL_USER_ACTIVE);
  4825. // Course 3.
  4826. $course3 = $this->getDataGenerator()->create_course();
  4827. $context3 = context_course::instance($course3->id);
  4828. // Student visits first two courses, course_get_recent_courses returns two courses.
  4829. $this->setUser($student);
  4830. course_view($context1);
  4831. course_view($context2);
  4832. $result = course_get_recent_courses($student->id);
  4833. $this->assertEqualsCanonicalizing([$course2->id, $course1->id], array_column($result, 'id'));
  4834. // Admin visits all three courses. Only the one with guest access is returned.
  4835. $this->setAdminUser();
  4836. course_view($context1);
  4837. course_view($context2);
  4838. course_view($context3);
  4839. $result = course_get_recent_courses(get_admin()->id);
  4840. $this->assertEqualsCanonicalizing([$course1->id], array_column($result, 'id'));
  4841. }
  4842. /**
  4843. * Test cases for the course_get_course_dates_for_user_ids tests.
  4844. */
  4845. public function get_course_get_course_dates_for_user_ids_test_cases() {
  4846. $now = time();
  4847. $pastcoursestart = $now - 100;
  4848. $futurecoursestart = $now + 100;
  4849. return [
  4850. 'future course start fixed no users enrolled' => [
  4851. 'relativedatemode' => false,
  4852. 'coursestart' => $futurecoursestart,
  4853. 'usercount' => 2,
  4854. 'enrolmentmethods' => [
  4855. ['manual', ENROL_INSTANCE_ENABLED],
  4856. ['self', ENROL_INSTANCE_ENABLED]
  4857. ],
  4858. 'enrolled' => [[], []],
  4859. 'expected' => [
  4860. [
  4861. 'start' => $futurecoursestart,
  4862. 'startoffset' => 0
  4863. ],
  4864. [
  4865. 'start' => $futurecoursestart,
  4866. 'startoffset' => 0
  4867. ]
  4868. ]
  4869. ],
  4870. 'future course start fixed 1 users enrolled future' => [
  4871. 'relativedatemode' => false,
  4872. 'coursestart' => $futurecoursestart,
  4873. 'usercount' => 2,
  4874. 'enrolmentmethods' => [
  4875. ['manual', ENROL_INSTANCE_ENABLED],
  4876. ['self', ENROL_INSTANCE_ENABLED]
  4877. ],
  4878. 'enrolled' => [
  4879. // User 1.
  4880. ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
  4881. // User 2.
  4882. []
  4883. ],
  4884. 'expected' => [
  4885. [
  4886. 'start' => $futurecoursestart,
  4887. 'startoffset' => 0
  4888. ],
  4889. [
  4890. 'start' => $futurecoursestart,
  4891. 'startoffset' => 0
  4892. ]
  4893. ]
  4894. ],
  4895. 'future course start fixed 1 users enrolled past' => [
  4896. 'relativedatemode' => false,
  4897. 'coursestart' => $futurecoursestart,
  4898. 'usercount' => 2,
  4899. 'enrolmentmethods' => [
  4900. ['manual', ENROL_INSTANCE_ENABLED],
  4901. ['self', ENROL_INSTANCE_ENABLED]
  4902. ],
  4903. 'enrolled' => [
  4904. // User 1.
  4905. ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]],
  4906. // User 2.
  4907. []
  4908. ],
  4909. 'expected' => [
  4910. [
  4911. 'start' => $futurecoursestart,
  4912. 'startoffset' => 0
  4913. ],
  4914. [
  4915. 'start' => $futurecoursestart,
  4916. 'startoffset' => 0
  4917. ]
  4918. ]
  4919. ],
  4920. 'future course start fixed 2 users enrolled future' => [
  4921. 'relativedatemode' => false,
  4922. 'coursestart' => $futurecoursestart,
  4923. 'usercount' => 2,
  4924. 'enrolmentmethods' => [
  4925. ['manual', ENROL_INSTANCE_ENABLED],
  4926. ['self', ENROL_INSTANCE_ENABLED]
  4927. ],
  4928. 'enrolled' => [
  4929. // User 1.
  4930. ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
  4931. // User 2.
  4932. ['manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]]
  4933. ],
  4934. 'expected' => [
  4935. [
  4936. 'start' => $futurecoursestart,
  4937. 'startoffset' => 0
  4938. ],
  4939. [
  4940. 'start' => $futurecoursestart,
  4941. 'startoffset' => 0
  4942. ]
  4943. ]
  4944. ],
  4945. 'future course start fixed 2 users enrolled past' => [
  4946. 'relativedatemode' => false,
  4947. 'coursestart' => $futurecoursestart,
  4948. 'usercount' => 2,
  4949. 'enrolmentmethods' => [
  4950. ['manual', ENROL_INSTANCE_ENABLED],
  4951. ['self', ENROL_INSTANCE_ENABLED]
  4952. ],
  4953. 'enrolled' => [
  4954. // User 1.
  4955. ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]],
  4956. // User 2.
  4957. ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]]
  4958. ],
  4959. 'expected' => [
  4960. [
  4961. 'start' => $futurecoursestart,
  4962. 'startoffset' => 0
  4963. ],
  4964. [
  4965. 'start' => $futurecoursestart,
  4966. 'startoffset' => 0
  4967. ]
  4968. ]
  4969. ],
  4970. 'future course start fixed 2 users enrolled mixed' => [
  4971. 'relativedatemode' => false,
  4972. 'coursestart' => $futurecoursestart,
  4973. 'usercount' => 2,
  4974. 'enrolmentmethods' => [
  4975. ['manual', ENROL_INSTANCE_ENABLED],
  4976. ['self', ENROL_INSTANCE_ENABLED]
  4977. ],
  4978. 'enrolled' => [
  4979. // User 1.
  4980. ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
  4981. // User 2.
  4982. ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]]
  4983. ],
  4984. 'expected' => [
  4985. [
  4986. 'start' => $futurecoursestart,
  4987. 'startoffset' => 0
  4988. ],
  4989. [
  4990. 'start' => $futurecoursestart,
  4991. 'startoffset' => 0
  4992. ]
  4993. ]
  4994. ],
  4995. 'future course start fixed 2 users enrolled 2 methods' => [
  4996. 'relativedatemode' => false,
  4997. 'coursestart' => $futurecoursestart,
  4998. 'usercount' => 2,
  4999. 'enrolmentmethods' => [
  5000. ['manual', ENROL_INSTANCE_ENABLED],
  5001. ['self', ENROL_INSTANCE_ENABLED]
  5002. ],
  5003. 'enrolled' => [
  5004. // User 1.
  5005. [
  5006. 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
  5007. 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
  5008. ],
  5009. // User 2.
  5010. [
  5011. 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
  5012. 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
  5013. ]
  5014. ],
  5015. 'expected' => [
  5016. [
  5017. 'start' => $futurecoursestart,
  5018. 'startoffset' => 0
  5019. ],
  5020. [
  5021. 'start' => $futurecoursestart,
  5022. 'startoffset' => 0
  5023. ]
  5024. ]
  5025. ],
  5026. 'future course start fixed 2 users enrolled 2 methods 1 disabled' => [
  5027. 'relativedatemode' => false,
  5028. 'coursestart' => $futurecoursestart,
  5029. 'usercount' => 2,
  5030. 'enrolmentmethods' => [
  5031. ['manual', ENROL_INSTANCE_DISABLED],
  5032. ['self', ENROL_INSTANCE_ENABLED]
  5033. ],
  5034. 'enrolled' => [
  5035. // User 1.
  5036. [
  5037. 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
  5038. 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
  5039. ],
  5040. // User 2.
  5041. [
  5042. 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
  5043. 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
  5044. ]
  5045. ],
  5046. 'expected' => [
  5047. [
  5048. 'start' => $futurecoursestart,
  5049. 'startoffset' => 0
  5050. ],
  5051. [
  5052. 'start' => $futurecoursestart,
  5053. 'startoffset' => 0
  5054. ]
  5055. ]
  5056. ],
  5057. 'future course start fixed 2 users enrolled 2 methods 2 disabled' => [
  5058. 'relativedatemode' => false,
  5059. 'coursestart' => $futurecoursestart,
  5060. 'usercount' => 2,
  5061. 'enrolmentmethods' => [
  5062. ['manual', ENROL_INSTANCE_DISABLED],
  5063. ['self', ENROL_INSTANCE_DISABLED]
  5064. ],
  5065. 'enrolled' => [
  5066. // User 1.
  5067. [
  5068. 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
  5069. 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
  5070. ],
  5071. // User 2.
  5072. [
  5073. 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
  5074. 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
  5075. ]
  5076. ],
  5077. 'expected' => [
  5078. [
  5079. 'start' => $futurecoursestart,
  5080. 'startoffset' => 0
  5081. ],
  5082. [
  5083. 'start' => $futurecoursestart,
  5084. 'startoffset' => 0
  5085. ]
  5086. ]
  5087. ],
  5088. 'future course start fixed 2 users enrolled 2 methods 0 disabled 1 user suspended' => [
  5089. 'relativedatemode' => false,
  5090. 'coursestart' => $futurecoursestart,
  5091. 'usercount' => 2,
  5092. 'enrolmentmethods' => [
  5093. ['manual', ENROL_INSTANCE_ENABLED],
  5094. ['self', ENROL_INSTANCE_ENABLED]
  5095. ],
  5096. 'enrolled' => [
  5097. // User 1.
  5098. [
  5099. 'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED],
  5100. 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
  5101. ],
  5102. // User 2.
  5103. [
  5104. 'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED],
  5105. 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
  5106. ]
  5107. ],
  5108. 'expected' => [
  5109. [
  5110. 'start' => $futurecoursestart,
  5111. 'startoffset' => 0
  5112. ],
  5113. [
  5114. 'start' => $futurecoursestart,
  5115. 'startoffset' => 0
  5116. ]
  5117. ]
  5118. ],
  5119. 'future course start fixed 2 users enrolled 2 methods 0 disabled 2 user suspended' => [
  5120. 'relativedatemode' => false,
  5121. 'coursestart' => $futurecoursestart,
  5122. 'usercount' => 2,
  5123. 'enrolmentmethods' => [
  5124. ['manual', ENROL_INSTANCE_ENABLED],
  5125. ['self', ENROL_INSTANCE_ENABLED]
  5126. ],
  5127. 'enrolled' => [
  5128. // User 1.
  5129. [
  5130. 'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED],
  5131. 'self' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED]
  5132. ],
  5133. // User 2.
  5134. [
  5135. 'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED],
  5136. 'self' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED]
  5137. ]
  5138. ],
  5139. 'expected' => [
  5140. [
  5141. 'start' => $futurecoursestart,
  5142. 'startoffset' => 0
  5143. ],
  5144. [
  5145. 'start' => $futurecoursestart,
  5146. 'startoffset' => 0
  5147. ]
  5148. ]
  5149. ],
  5150. 'future course start relative no users enrolled' => [
  5151. 'relativedatemode' => true,
  5152. 'coursestart' => $futurecoursestart,
  5153. 'usercount' => 2,
  5154. 'enrolmentmethods' => [
  5155. ['manual', ENROL_INSTANCE_ENABLED],
  5156. ['self', ENROL_INSTANCE_ENABLED]
  5157. ],
  5158. 'enrolled' => [[], []],
  5159. 'expected' => [
  5160. [
  5161. 'start' => $futurecoursestart,
  5162. 'startoffset' => 0
  5163. ],
  5164. [
  5165. 'start' => $futurecoursestart,
  5166. 'startoffset' => 0
  5167. ]
  5168. ]
  5169. ],
  5170. 'future course start relative 1 users enrolled future' => [
  5171. 'relativedatemode' => true,
  5172. 'coursestart' => $futurecoursestart,
  5173. 'usercount' => 2,
  5174. 'enrolmentmethods' => [
  5175. ['manual', ENROL_INSTANCE_ENABLED],
  5176. ['self', ENROL_INSTANCE_ENABLED]
  5177. ],
  5178. 'enrolled' => [
  5179. // User 1.
  5180. ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
  5181. // User 2.
  5182. []
  5183. ],
  5184. 'expected' => [
  5185. [
  5186. 'start' => $futurecoursestart + 10,
  5187. 'startoffset' => 10
  5188. ],
  5189. [
  5190. 'start' => $futurecoursestart,
  5191. 'startoffset' => 0
  5192. ]
  5193. ]
  5194. ],
  5195. 'future course start relative 1 users enrolled past' => [
  5196. 'relativedatemode' => true,
  5197. 'coursestart' => $futurecoursestart,
  5198. 'usercount' => 2,
  5199. 'enrolmentmethods' => [
  5200. ['manual', ENROL_INSTANCE_ENABLED],
  5201. ['self', ENROL_INSTANCE_ENABLED]
  5202. ],
  5203. 'enrolled' => [
  5204. // User 1.
  5205. ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]],
  5206. // User 2.
  5207. []
  5208. ],
  5209. 'expected' => [
  5210. [
  5211. 'start' => $futurecoursestart,
  5212. 'startoffset' => 0
  5213. ],
  5214. [
  5215. 'start' => $futurecoursestart,
  5216. 'startoffset' => 0
  5217. ]
  5218. ]
  5219. ],
  5220. 'future course start relative 2 users enrolled future' => [
  5221. 'relativedatemode' => true,
  5222. 'coursestart' => $futurecoursestart,
  5223. 'usercount' => 2,
  5224. 'enrolmentmethods' => [
  5225. ['manual', ENROL_INSTANCE_ENABLED],
  5226. ['self', ENROL_INSTANCE_ENABLED]
  5227. ],
  5228. 'enrolled' => [
  5229. // User 1.
  5230. ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
  5231. // User 2.
  5232. ['manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]]
  5233. ],
  5234. 'expected' => [
  5235. [
  5236. 'start' => $futurecoursestart + 10,
  5237. 'startoffset' => 10
  5238. ],
  5239. [
  5240. 'start' => $futurecoursestart + 20,
  5241. 'startoffset' => 20
  5242. ]
  5243. ]
  5244. ],
  5245. 'future course start relative 2 users enrolled past' => [
  5246. 'relativedatemode' => true,
  5247. 'coursestart' => $futurecoursestart,
  5248. 'usercount' => 2,
  5249. 'enrolmentmethods' => [
  5250. ['manual', ENROL_INSTANCE_ENABLED],
  5251. ['self', ENROL_INSTANCE_ENABLED]
  5252. ],
  5253. 'enrolled' => [
  5254. // User 1.
  5255. ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]],
  5256. // User 2.
  5257. ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]]
  5258. ],
  5259. 'expected' => [
  5260. [
  5261. 'start' => $futurecoursestart,
  5262. 'startoffset' => 0
  5263. ],
  5264. [
  5265. 'start' => $futurecoursestart,
  5266. 'startoffset' => 0
  5267. ]
  5268. ]
  5269. ],
  5270. 'future course start relative 2 users enrolled mixed' => [
  5271. 'relativedatemode' => true,
  5272. 'coursestart' => $futurecoursestart,
  5273. 'usercount' => 2,
  5274. 'enrolmentmethods' => [
  5275. ['manual', ENROL_INSTANCE_ENABLED],
  5276. ['self', ENROL_INSTANCE_ENABLED]
  5277. ],
  5278. 'enrolled' => [
  5279. // User 1.
  5280. ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
  5281. // User 2.
  5282. ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]]
  5283. ],
  5284. 'expected' => [
  5285. [
  5286. 'start' => $futurecoursestart + 10,
  5287. 'startoffset' => 10
  5288. ],
  5289. [
  5290. 'start' => $futurecoursestart,
  5291. 'startoffset' => 0
  5292. ]
  5293. ]
  5294. ],
  5295. 'future course start relative 2 users enrolled 2 methods' => [
  5296. 'relativedatemode' => true,
  5297. 'coursestart' => $futurecoursestart,
  5298. 'usercount' => 2,
  5299. 'enrolmentmethods' => [
  5300. ['manual', ENROL_INSTANCE_ENABLED],
  5301. ['self', ENROL_INSTANCE_ENABLED]
  5302. ],
  5303. 'enrolled' => [
  5304. // User 1.
  5305. [
  5306. 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
  5307. 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
  5308. ],
  5309. // User 2.
  5310. [
  5311. 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
  5312. 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
  5313. ]
  5314. ],
  5315. 'expected' => [
  5316. [
  5317. 'start' => $futurecoursestart + 10,
  5318. 'startoffset' => 10
  5319. ],
  5320. [
  5321. 'start' => $futurecoursestart + 10,
  5322. 'startoffset' => 10
  5323. ]
  5324. ]
  5325. ],
  5326. 'future course start relative 2 users enrolled 2 methods 1 disabled' => [
  5327. 'relativedatemode' => true,
  5328. 'coursestart' => $futurecoursestart,
  5329. 'usercount' => 2,
  5330. 'enrolmentmethods' => [
  5331. ['manual', ENROL_INSTANCE_DISABLED],
  5332. ['self', ENROL_INSTANCE_ENABLED]
  5333. ],
  5334. 'enrolled' => [
  5335. // User 1.
  5336. [
  5337. 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
  5338. 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
  5339. ],
  5340. // User 2.
  5341. [
  5342. 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
  5343. 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
  5344. ]
  5345. ],
  5346. 'expected' => [
  5347. [
  5348. 'start' => $futurecoursestart + 20,
  5349. 'startoffset' => 20
  5350. ],
  5351. [
  5352. 'start' => $futurecoursestart + 10,
  5353. 'startoffset' => 10
  5354. ]
  5355. ]
  5356. ],
  5357. 'future course start relative 2 users enrolled 2 methods 2 disabled' => [
  5358. 'relativedatemode' => true,
  5359. 'coursestart' => $futurecoursestart,
  5360. 'usercount' => 2,
  5361. 'enrolmentmethods' => [
  5362. ['manual', ENROL_INSTANCE_DISABLED],
  5363. ['self', ENROL_INSTANCE_DISABLED]
  5364. ],
  5365. 'enrolled' => [
  5366. // User 1.
  5367. [
  5368. 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
  5369. 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
  5370. ],
  5371. // User 2.
  5372. [
  5373. 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
  5374. 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
  5375. ]
  5376. ],
  5377. 'expected' => [
  5378. [
  5379. 'start' => $futurecoursestart,
  5380. 'startoffset' => 0
  5381. ],
  5382. [
  5383. 'start' => $futurecoursestart,
  5384. 'startoffset' => 0
  5385. ]
  5386. ]
  5387. ],
  5388. 'future course start relative 2 users enrolled 2 methods 0 disabled 1 user suspended' => [
  5389. 'relativedatemode' => true,
  5390. 'coursestart' => $futurecoursestart,
  5391. 'usercount' => 2,
  5392. 'enrolmentmethods' => [
  5393. ['manual', ENROL_INSTANCE_ENABLED],
  5394. ['self', ENROL_INSTANCE_ENABLED]
  5395. ],
  5396. 'enrolled' => [
  5397. // User 1.
  5398. [
  5399. 'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED],
  5400. 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
  5401. ],
  5402. // User 2.
  5403. [
  5404. 'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED],
  5405. 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
  5406. ]
  5407. ],
  5408. 'expected' => [
  5409. [
  5410. 'start' => $futurecoursestart + 20,
  5411. 'startoffset' => 20
  5412. ],
  5413. [
  5414. 'start' => $futurecoursestart + 10,
  5415. 'startoffset' => 10
  5416. ]
  5417. ]
  5418. ],
  5419. 'future course start relative 2 users enrolled 2 methods 0 disabled 2 user suspended' => [
  5420. 'relativedatemode' => true,
  5421. 'coursestart' => $futurecoursestart,
  5422. 'usercount' => 2,
  5423. 'enrolmentmethods' => [
  5424. ['manual', ENROL_INSTANCE_ENABLED],
  5425. ['self', ENROL_INSTANCE_ENABLED]
  5426. ],
  5427. 'enrolled' => [
  5428. // User 1.
  5429. [
  5430. 'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED],
  5431. 'self' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED]
  5432. ],
  5433. // User 2.
  5434. [
  5435. 'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED],
  5436. 'self' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED]
  5437. ]
  5438. ],
  5439. 'expected' => [
  5440. [
  5441. 'start' => $futurecoursestart,
  5442. 'startoffset' => 0
  5443. ],
  5444. [
  5445. 'start' => $futurecoursestart,
  5446. 'startoffset' => 0
  5447. ]
  5448. ]
  5449. ],
  5450. // Course start date in the past.
  5451. 'past course start fixed no users enrolled' => [
  5452. 'relativedatemode' => false,
  5453. 'coursestart' => $pastcoursestart,
  5454. 'usercount' => 2,
  5455. 'enrolmentmethods' => [
  5456. ['manual', ENROL_INSTANCE_ENABLED],
  5457. ['self', ENROL_INSTANCE_ENABLED]
  5458. ],
  5459. 'enrolled' => [[], []],
  5460. 'expected' => [
  5461. [
  5462. 'start' => $pastcoursestart,
  5463. 'startoffset' => 0
  5464. ],
  5465. [
  5466. 'start' => $pastcoursestart,
  5467. 'startoffset' => 0
  5468. ]
  5469. ]
  5470. ],
  5471. 'past course start fixed 1 users enrolled future' => [
  5472. 'relativedatemode' => false,
  5473. 'coursestart' => $pastcoursestart,
  5474. 'usercount' => 2,
  5475. 'enrolmentmethods' => [
  5476. ['manual', ENROL_INSTANCE_ENABLED],
  5477. ['self', ENROL_INSTANCE_ENABLED]
  5478. ],
  5479. 'enrolled' => [
  5480. // User 1.
  5481. ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
  5482. // User 2.
  5483. []
  5484. ],
  5485. 'expected' => [
  5486. [
  5487. 'start' => $pastcoursestart,
  5488. 'startoffset' => 0
  5489. ],
  5490. [
  5491. 'start' => $pastcoursestart,
  5492. 'startoffset' => 0
  5493. ]
  5494. ]
  5495. ],
  5496. 'past course start fixed 1 users enrolled past' => [
  5497. 'relativedatemode' => false,
  5498. 'coursestart' => $pastcoursestart,
  5499. 'usercount' => 2,
  5500. 'enrolmentmethods' => [
  5501. ['manual', ENROL_INSTANCE_ENABLED],
  5502. ['self', ENROL_INSTANCE_ENABLED]
  5503. ],
  5504. 'enrolled' => [
  5505. // User 1.
  5506. ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]],
  5507. // User 2.
  5508. []
  5509. ],
  5510. 'expected' => [
  5511. [
  5512. 'start' => $pastcoursestart,
  5513. 'startoffset' => 0
  5514. ],
  5515. [
  5516. 'start' => $pastcoursestart,
  5517. 'startoffset' => 0
  5518. ]
  5519. ]
  5520. ],
  5521. 'past course start fixed 2 users enrolled future' => [
  5522. 'relativedatemode' => false,
  5523. 'coursestart' => $pastcoursestart,
  5524. 'usercount' => 2,
  5525. 'enrolmentmethods' => [
  5526. ['manual', ENROL_INSTANCE_ENABLED],
  5527. ['self', ENROL_INSTANCE_ENABLED]
  5528. ],
  5529. 'enrolled' => [
  5530. // User 1.
  5531. ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
  5532. // User 2.
  5533. ['manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]]
  5534. ],
  5535. 'expected' => [
  5536. [
  5537. 'start' => $pastcoursestart,
  5538. 'startoffset' => 0
  5539. ],
  5540. [
  5541. 'start' => $pastcoursestart,
  5542. 'startoffset' => 0
  5543. ]
  5544. ]
  5545. ],
  5546. 'past course start fixed 2 users enrolled past' => [
  5547. 'relativedatemode' => false,
  5548. 'coursestart' => $pastcoursestart,
  5549. 'usercount' => 2,
  5550. 'enrolmentmethods' => [
  5551. ['manual', ENROL_INSTANCE_ENABLED],
  5552. ['self', ENROL_INSTANCE_ENABLED]
  5553. ],
  5554. 'enrolled' => [
  5555. // User 1.
  5556. ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]],
  5557. // User 2.
  5558. ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]]
  5559. ],
  5560. 'expected' => [
  5561. [
  5562. 'start' => $pastcoursestart,
  5563. 'startoffset' => 0
  5564. ],
  5565. [
  5566. 'start' => $pastcoursestart,
  5567. 'startoffset' => 0
  5568. ]
  5569. ]
  5570. ],
  5571. 'past course start fixed 2 users enrolled mixed' => [
  5572. 'relativedatemode' => false,
  5573. 'coursestart' => $pastcoursestart,
  5574. 'usercount' => 2,
  5575. 'enrolmentmethods' => [
  5576. ['manual', ENROL_INSTANCE_ENABLED],
  5577. ['self', ENROL_INSTANCE_ENABLED]
  5578. ],
  5579. 'enrolled' => [
  5580. // User 1.
  5581. ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
  5582. // User 2.
  5583. ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]]
  5584. ],
  5585. 'expected' => [
  5586. [
  5587. 'start' => $pastcoursestart,
  5588. 'startoffset' => 0
  5589. ],
  5590. [
  5591. 'start' => $pastcoursestart,
  5592. 'startoffset' => 0
  5593. ]
  5594. ]
  5595. ],
  5596. 'past course start fixed 2 users enrolled 2 methods' => [
  5597. 'relativedatemode' => false,
  5598. 'coursestart' => $pastcoursestart,
  5599. 'usercount' => 2,
  5600. 'enrolmentmethods' => [
  5601. ['manual', ENROL_INSTANCE_ENABLED],
  5602. ['self', ENROL_INSTANCE_ENABLED]
  5603. ],
  5604. 'enrolled' => [
  5605. // User 1.
  5606. [
  5607. 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
  5608. 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
  5609. ],
  5610. // User 2.
  5611. [
  5612. 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
  5613. 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
  5614. ]
  5615. ],
  5616. 'expected' => [
  5617. [
  5618. 'start' => $pastcoursestart,
  5619. 'startoffset' => 0
  5620. ],
  5621. [
  5622. 'start' => $pastcoursestart,
  5623. 'startoffset' => 0
  5624. ]
  5625. ]
  5626. ],
  5627. 'past course start fixed 2 users enrolled 2 methods 1 disabled' => [
  5628. 'relativedatemode' => false,
  5629. 'coursestart' => $pastcoursestart,
  5630. 'usercount' => 2,
  5631. 'enrolmentmethods' => [
  5632. ['manual', ENROL_INSTANCE_DISABLED],
  5633. ['self', ENROL_INSTANCE_ENABLED]
  5634. ],
  5635. 'enrolled' => [
  5636. // User 1.
  5637. [
  5638. 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
  5639. 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
  5640. ],
  5641. // User 2.
  5642. [
  5643. 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
  5644. 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
  5645. ]
  5646. ],
  5647. 'expected' => [
  5648. [
  5649. 'start' => $pastcoursestart,
  5650. 'startoffset' => 0
  5651. ],
  5652. [
  5653. 'start' => $pastcoursestart,
  5654. 'startoffset' => 0
  5655. ]
  5656. ]
  5657. ],
  5658. 'past course start fixed 2 users enrolled 2 methods 2 disabled' => [
  5659. 'relativedatemode' => false,
  5660. 'coursestart' => $pastcoursestart,
  5661. 'usercount' => 2,
  5662. 'enrolmentmethods' => [
  5663. ['manual', ENROL_INSTANCE_DISABLED],
  5664. ['self', ENROL_INSTANCE_DISABLED]
  5665. ],
  5666. 'enrolled' => [
  5667. // User 1.
  5668. [
  5669. 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
  5670. 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
  5671. ],
  5672. // User 2.
  5673. [
  5674. 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
  5675. 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
  5676. ]
  5677. ],
  5678. 'expected' => [
  5679. [
  5680. 'start' => $pastcoursestart,
  5681. 'startoffset' => 0
  5682. ],
  5683. [
  5684. 'start' => $pastcoursestart,
  5685. 'startoffset' => 0
  5686. ]
  5687. ]
  5688. ],
  5689. 'past course start fixed 2 users enrolled 2 methods 0 disabled 1 user suspended' => [
  5690. 'relativedatemode' => false,
  5691. 'coursestart' => $pastcoursestart,
  5692. 'usercount' => 2,
  5693. 'enrolmentmethods' => [
  5694. ['manual', ENROL_INSTANCE_ENABLED],
  5695. ['self', ENROL_INSTANCE_ENABLED]
  5696. ],
  5697. 'enrolled' => [
  5698. // User 1.
  5699. [
  5700. 'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED],
  5701. 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
  5702. ],
  5703. // User 2.
  5704. [
  5705. 'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED],
  5706. 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
  5707. ]
  5708. ],
  5709. 'expected' => [
  5710. [
  5711. 'start' => $pastcoursestart,
  5712. 'startoffset' => 0
  5713. ],
  5714. [
  5715. 'start' => $pastcoursestart,
  5716. 'startoffset' => 0
  5717. ]
  5718. ]
  5719. ],
  5720. 'past course start fixed 2 users enrolled 2 methods 0 disabled 2 user suspended' => [
  5721. 'relativedatemode' => false,
  5722. 'coursestart' => $pastcoursestart,
  5723. 'usercount' => 2,
  5724. 'enrolmentmethods' => [
  5725. ['manual', ENROL_INSTANCE_ENABLED],
  5726. ['self', ENROL_INSTANCE_ENABLED]
  5727. ],
  5728. 'enrolled' => [
  5729. // User 1.
  5730. [
  5731. 'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED],
  5732. 'self' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED]
  5733. ],
  5734. // User 2.
  5735. [
  5736. 'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED],
  5737. 'self' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED]
  5738. ]
  5739. ],
  5740. 'expected' => [
  5741. [
  5742. 'start' => $pastcoursestart,
  5743. 'startoffset' => 0
  5744. ],
  5745. [
  5746. 'start' => $pastcoursestart,
  5747. 'startoffset' => 0
  5748. ]
  5749. ]
  5750. ],
  5751. 'past course start relative no users enrolled' => [
  5752. 'relativedatemode' => true,
  5753. 'coursestart' => $pastcoursestart,
  5754. 'usercount' => 2,
  5755. 'enrolmentmethods' => [
  5756. ['manual', ENROL_INSTANCE_ENABLED],
  5757. ['self', ENROL_INSTANCE_ENABLED]
  5758. ],
  5759. 'enrolled' => [[], []],
  5760. 'expected' => [
  5761. [
  5762. 'start' => $pastcoursestart,
  5763. 'startoffset' => 0
  5764. ],
  5765. [
  5766. 'start' => $pastcoursestart,
  5767. 'startoffset' => 0
  5768. ]
  5769. ]
  5770. ],
  5771. 'past course start relative 1 users enrolled future' => [
  5772. 'relativedatemode' => true,
  5773. 'coursestart' => $pastcoursestart,
  5774. 'usercount' => 2,
  5775. 'enrolmentmethods' => [
  5776. ['manual', ENROL_INSTANCE_ENABLED],
  5777. ['self', ENROL_INSTANCE_ENABLED]
  5778. ],
  5779. 'enrolled' => [
  5780. // User 1.
  5781. ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
  5782. // User 2.
  5783. []
  5784. ],
  5785. 'expected' => [
  5786. [
  5787. 'start' => $pastcoursestart + 10,
  5788. 'startoffset' => 10
  5789. ],
  5790. [
  5791. 'start' => $pastcoursestart,
  5792. 'startoffset' => 0
  5793. ]
  5794. ]
  5795. ],
  5796. 'past course start relative 1 users enrolled past' => [
  5797. 'relativedatemode' => true,
  5798. 'coursestart' => $pastcoursestart,
  5799. 'usercount' => 2,
  5800. 'enrolmentmethods' => [
  5801. ['manual', ENROL_INSTANCE_ENABLED],
  5802. ['self', ENROL_INSTANCE_ENABLED]
  5803. ],
  5804. 'enrolled' => [
  5805. // User 1.
  5806. ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]],
  5807. // User 2.
  5808. []
  5809. ],
  5810. 'expected' => [
  5811. [
  5812. 'start' => $pastcoursestart,
  5813. 'startoffset' => 0
  5814. ],
  5815. [
  5816. 'start' => $pastcoursestart,
  5817. 'startoffset' => 0
  5818. ]
  5819. ]
  5820. ],
  5821. 'past course start relative 2 users enrolled future' => [
  5822. 'relativedatemode' => true,
  5823. 'coursestart' => $pastcoursestart,
  5824. 'usercount' => 2,
  5825. 'enrolmentmethods' => [
  5826. ['manual', ENROL_INSTANCE_ENABLED],
  5827. ['self', ENROL_INSTANCE_ENABLED]
  5828. ],
  5829. 'enrolled' => [
  5830. // User 1.
  5831. ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
  5832. // User 2.
  5833. ['manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]]
  5834. ],
  5835. 'expected' => [
  5836. [
  5837. 'start' => $pastcoursestart + 10,
  5838. 'startoffset' => 10
  5839. ],
  5840. [
  5841. 'start' => $pastcoursestart + 20,
  5842. 'startoffset' => 20
  5843. ]
  5844. ]
  5845. ],
  5846. 'past course start relative 2 users enrolled past' => [
  5847. 'relativedatemode' => true,
  5848. 'coursestart' => $pastcoursestart,
  5849. 'usercount' => 2,
  5850. 'enrolmentmethods' => [
  5851. ['manual', ENROL_INSTANCE_ENABLED],
  5852. ['self', ENROL_INSTANCE_ENABLED]
  5853. ],
  5854. 'enrolled' => [
  5855. // User 1.
  5856. ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]],
  5857. // User 2.
  5858. ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]]
  5859. ],
  5860. 'expected' => [
  5861. [
  5862. 'start' => $pastcoursestart,
  5863. 'startoffset' => 0
  5864. ],
  5865. [
  5866. 'start' => $pastcoursestart,
  5867. 'startoffset' => 0
  5868. ]
  5869. ]
  5870. ],
  5871. 'past course start relative 2 users enrolled mixed' => [
  5872. 'relativedatemode' => true,
  5873. 'coursestart' => $pastcoursestart,
  5874. 'usercount' => 2,
  5875. 'enrolmentmethods' => [
  5876. ['manual', ENROL_INSTANCE_ENABLED],
  5877. ['self', ENROL_INSTANCE_ENABLED]
  5878. ],
  5879. 'enrolled' => [
  5880. // User 1.
  5881. ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
  5882. // User 2.
  5883. ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]]
  5884. ],
  5885. 'expected' => [
  5886. [
  5887. 'start' => $pastcoursestart + 10,
  5888. 'startoffset' => 10
  5889. ],
  5890. [
  5891. 'start' => $pastcoursestart,
  5892. 'startoffset' => 0
  5893. ]
  5894. ]
  5895. ],
  5896. 'past course start relative 2 users enrolled 2 methods' => [
  5897. 'relativedatemode' => true,
  5898. 'coursestart' => $pastcoursestart,
  5899. 'usercount' => 2,
  5900. 'enrolmentmethods' => [
  5901. ['manual', ENROL_INSTANCE_ENABLED],
  5902. ['self', ENROL_INSTANCE_ENABLED]
  5903. ],
  5904. 'enrolled' => [
  5905. // User 1.
  5906. [
  5907. 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
  5908. 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
  5909. ],
  5910. // User 2.
  5911. [
  5912. 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
  5913. 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
  5914. ]
  5915. ],
  5916. 'expected' => [
  5917. [
  5918. 'start' => $pastcoursestart + 10,
  5919. 'startoffset' => 10
  5920. ],
  5921. [
  5922. 'start' => $pastcoursestart + 10,
  5923. 'startoffset' => 10
  5924. ]
  5925. ]
  5926. ],
  5927. 'past course start relative 2 users enrolled 2 methods 1 disabled' => [
  5928. 'relativedatemode' => true,
  5929. 'coursestart' => $pastcoursestart,
  5930. 'usercount' => 2,
  5931. 'enrolmentmethods' => [
  5932. ['manual', ENROL_INSTANCE_DISABLED],
  5933. ['self', ENROL_INSTANCE_ENABLED]
  5934. ],
  5935. 'enrolled' => [
  5936. // User 1.
  5937. [
  5938. 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
  5939. 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
  5940. ],
  5941. // User 2.
  5942. [
  5943. 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
  5944. 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
  5945. ]
  5946. ],
  5947. 'expected' => [
  5948. [
  5949. 'start' => $pastcoursestart + 20,
  5950. 'startoffset' => 20
  5951. ],
  5952. [
  5953. 'start' => $pastcoursestart + 10,
  5954. 'startoffset' => 10
  5955. ]
  5956. ]
  5957. ],
  5958. 'past course start relative 2 users enrolled 2 methods 2 disabled' => [
  5959. 'relativedatemode' => true,
  5960. 'coursestart' => $pastcoursestart,
  5961. 'usercount' => 2,
  5962. 'enrolmentmethods' => [
  5963. ['manual', ENROL_INSTANCE_DISABLED],
  5964. ['self', ENROL_INSTANCE_DISABLED]
  5965. ],
  5966. 'enrolled' => [
  5967. // User 1.
  5968. [
  5969. 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
  5970. 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
  5971. ],
  5972. // User 2.
  5973. [
  5974. 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
  5975. 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
  5976. ]
  5977. ],
  5978. 'expected' => [
  5979. [
  5980. 'start' => $pastcoursestart,
  5981. 'startoffset' => 0
  5982. ],
  5983. [
  5984. 'start' => $pastcoursestart,
  5985. 'startoffset' => 0
  5986. ]
  5987. ]
  5988. ],
  5989. 'past course start relative 2 users enrolled 2 methods 0 disabled 1 user suspended' => [
  5990. 'relativedatemode' => true,
  5991. 'coursestart' => $pastcoursestart,
  5992. 'usercount' => 2,
  5993. 'enrolmentmethods' => [
  5994. ['manual', ENROL_INSTANCE_ENABLED],
  5995. ['self', ENROL_INSTANCE_ENABLED]
  5996. ],
  5997. 'enrolled' => [
  5998. // User 1.
  5999. [
  6000. 'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED],
  6001. 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
  6002. ],
  6003. // User 2.
  6004. [
  6005. 'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED],
  6006. 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
  6007. ]
  6008. ],
  6009. 'expected' => [
  6010. [
  6011. 'start' => $pastcoursestart + 20,
  6012. 'startoffset' => 20
  6013. ],
  6014. [
  6015. 'start' => $pastcoursestart + 10,
  6016. 'startoffset' => 10
  6017. ]
  6018. ]
  6019. ],
  6020. 'past course start relative 2 users enrolled 2 methods 0 disabled 2 user suspended' => [
  6021. 'relativedatemode' => true,
  6022. 'coursestart' => $pastcoursestart,
  6023. 'usercount' => 2,
  6024. 'enrolmentmethods' => [
  6025. ['manual', ENROL_INSTANCE_ENABLED],
  6026. ['self', ENROL_INSTANCE_ENABLED]
  6027. ],
  6028. 'enrolled' => [
  6029. // User 1.
  6030. [
  6031. 'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED],
  6032. 'self' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED]
  6033. ],
  6034. // User 2.
  6035. [
  6036. 'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED],
  6037. 'self' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED]
  6038. ]
  6039. ],
  6040. 'expected' => [
  6041. [
  6042. 'start' => $pastcoursestart,
  6043. 'startoffset' => 0
  6044. ],
  6045. [
  6046. 'start' => $pastcoursestart,
  6047. 'startoffset' => 0
  6048. ]
  6049. ]
  6050. ]
  6051. ];
  6052. }
  6053. /**
  6054. * Test the course_get_course_dates_for_user_ids function.
  6055. *
  6056. * @dataProvider get_course_get_course_dates_for_user_ids_test_cases()
  6057. * @param bool $relativedatemode Set the course to relative dates mode
  6058. * @param int $coursestart Course start date
  6059. * @param int $usercount Number of users to create
  6060. * @param array $enrolmentmethods Enrolment methods to set for the course
  6061. * @param array $enrolled Enrolment config for to set for the users
  6062. * @param array $expected Expected output
  6063. */
  6064. public function test_course_get_course_dates_for_user_ids(
  6065. $relativedatemode,
  6066. $coursestart,
  6067. $usercount,
  6068. $enrolmentmethods,
  6069. $enrolled,
  6070. $expected
  6071. ) {
  6072. global $DB;
  6073. $this->resetAfterTest();
  6074. $generator = $this->getDataGenerator();
  6075. $course = $generator->create_course(['startdate' => $coursestart]);
  6076. $course->relativedatesmode = $relativedatemode;
  6077. $users = [];
  6078. for ($i = 0; $i < $usercount; $i++) {
  6079. $users[] = $generator->create_user();
  6080. }
  6081. foreach ($enrolmentmethods as [$type, $status]) {
  6082. $record = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => $type]);
  6083. $plugin = enrol_get_plugin($type);
  6084. if ($record->status != $status) {
  6085. $plugin->update_status($record, $status);
  6086. }
  6087. }
  6088. foreach ($enrolled as $index => $enrolconfig) {
  6089. $user = $users[$index];
  6090. foreach ($enrolconfig as $type => [$starttime, $status]) {
  6091. $generator->enrol_user($user->id, $course->id, 'student', $type, $starttime, 0, $status);
  6092. }
  6093. }
  6094. $userids = array_map(function($user) {
  6095. return $user->id;
  6096. }, $users);
  6097. $actual = course_get_course_dates_for_user_ids($course, $userids);
  6098. foreach ($expected as $index => $exp) {
  6099. $userid = $userids[$index];
  6100. $act = $actual[$userid];
  6101. $this->assertEquals($exp['start'], $act['start']);
  6102. $this->assertEquals($exp['startoffset'], $act['startoffset']);
  6103. }
  6104. }
  6105. /**
  6106. * Test that calling course_get_course_dates_for_user_ids multiple times in the
  6107. * same request fill fetch the correct data for the user.
  6108. */
  6109. public function test_course_get_course_dates_for_user_ids_multiple_calls() {
  6110. $this->resetAfterTest();
  6111. $generator = $this->getDataGenerator();
  6112. $now = time();
  6113. $coursestart = $now - 1000;
  6114. $course = $generator->create_course(['startdate' => $coursestart]);
  6115. $course->relativedatesmode = true;
  6116. $user1 = $generator->create_user();
  6117. $user2 = $generator->create_user();
  6118. $user1start = $coursestart + 100;
  6119. $user2start = $coursestart + 200;
  6120. $generator->enrol_user($user1->id, $course->id, 'student', 'manual', $user1start);
  6121. $generator->enrol_user($user2->id, $course->id, 'student', 'manual', $user2start);
  6122. $result = course_get_course_dates_for_user_ids($course, [$user1->id]);
  6123. $this->assertEquals($user1start, $result[$user1->id]['start']);
  6124. $result = course_get_course_dates_for_user_ids($course, [$user1->id, $user2->id]);
  6125. $this->assertEquals($user1start, $result[$user1->id]['start']);
  6126. $this->assertEquals($user2start, $result[$user2->id]['start']);
  6127. $result = course_get_course_dates_for_user_ids($course, [$user2->id]);
  6128. $this->assertEquals($user2start, $result[$user2->id]['start']);
  6129. }
  6130. /**
  6131. * Data provider for test_course_modules_pending_deletion.
  6132. *
  6133. * @return array An array of arrays contain test data
  6134. */
  6135. public function provider_course_modules_pending_deletion() {
  6136. return [
  6137. 'Non-gradable activity, check all' => [['forum'], 0, false, true],
  6138. 'Gradable activity, check all' => [['assign'], 0, false, true],
  6139. 'Non-gradable activity, check gradables' => [['forum'], 0, true, false],
  6140. 'Gradable activity, check gradables' => [['assign'], 0, true, true],
  6141. 'Non-gradable within multiple, check all' => [['quiz', 'forum', 'assign'], 1, false, true],
  6142. 'Non-gradable within multiple, check gradables' => [['quiz', 'forum', 'assign'], 1, true, false],
  6143. 'Gradable within multiple, check all' => [['quiz', 'forum', 'assign'], 2, false, true],
  6144. 'Gradable within multiple, check gradables' => [['quiz', 'forum', 'assign'], 2, true, true],
  6145. ];
  6146. }
  6147. /**
  6148. * Tests the function course_modules_pending_deletion.
  6149. *
  6150. * @param string[] $modules A complete list aff all available modules before deletion
  6151. * @param int $indextodelete The index of the module in the $modules array that we want to test with
  6152. * @param bool $gradable The value to pass to the gradable argument of the course_modules_pending_deletion function
  6153. * @param bool $expected The expected result
  6154. * @dataProvider provider_course_modules_pending_deletion
  6155. */
  6156. public function test_course_modules_pending_deletion(array $modules, int $indextodelete, bool $gradable, bool $expected) {
  6157. $this->resetAfterTest();
  6158. // Ensure recyclebin is enabled.
  6159. set_config('coursebinenable', true, 'tool_recyclebin');
  6160. // Create course and modules.
  6161. $generator = $this->getDataGenerator();
  6162. $course = $generator->create_course();
  6163. $moduleinstances = [];
  6164. foreach ($modules as $module) {
  6165. $moduleinstances[] = $generator->create_module($module, array('course' => $course->id));
  6166. }
  6167. course_delete_module($moduleinstances[$indextodelete]->cmid, true); // Try to delete the instance asynchronously.
  6168. $this->assertEquals($expected, course_modules_pending_deletion($course->id, $gradable));
  6169. }
  6170. /**
  6171. * Tests for the course_request::can_request
  6172. */
  6173. public function test_can_request_course() {
  6174. global $CFG, $DB;
  6175. $this->resetAfterTest();
  6176. $user = $this->getDataGenerator()->create_user();
  6177. $cat1 = $CFG->defaultrequestcategory;
  6178. $cat2 = $this->getDataGenerator()->create_category()->id;
  6179. $cat3 = $this->getDataGenerator()->create_category()->id;
  6180. $context1 = context_coursecat::instance($cat1);
  6181. $context2 = context_coursecat::instance($cat2);
  6182. $context3 = context_coursecat::instance($cat3);
  6183. $this->setUser($user);
  6184. // By default users don't have capability to request courses.
  6185. $this->assertFalse(course_request::can_request(context_system::instance()));
  6186. $this->assertFalse(course_request::can_request($context1));
  6187. $this->assertFalse(course_request::can_request($context2));
  6188. $this->assertFalse(course_request::can_request($context3));
  6189. // Allow for the 'user' role the capability to request courses.
  6190. $userroleid = $DB->get_field('role', 'id', ['shortname' => 'user']);
  6191. assign_capability('moodle/course:request', CAP_ALLOW, $userroleid,
  6192. context_system::instance()->id);
  6193. accesslib_clear_all_caches_for_unit_testing();
  6194. // Lock category selection.
  6195. $CFG->lockrequestcategory = 1;
  6196. // Now user can only request course in the default category or in system context.
  6197. $this->assertTrue(course_request::can_request(context_system::instance()));
  6198. $this->assertTrue(course_request::can_request($context1));
  6199. $this->assertFalse(course_request::can_request($context2));
  6200. $this->assertFalse(course_request::can_request($context3));
  6201. // Enable category selection. User can request course anywhere.
  6202. $CFG->lockrequestcategory = 0;
  6203. $this->assertTrue(course_request::can_request(context_system::instance()));
  6204. $this->assertTrue(course_request::can_request($context1));
  6205. $this->assertTrue(course_request::can_request($context2));
  6206. $this->assertTrue(course_request::can_request($context3));
  6207. // Remove cap from cat2.
  6208. $roleid = create_role('Test role', 'testrole', 'Test role description');
  6209. assign_capability('moodle/course:request', CAP_PROHIBIT, $roleid,
  6210. $context2->id, true);
  6211. role_assign($roleid, $user->id, $context2->id);
  6212. accesslib_clear_all_caches_for_unit_testing();
  6213. $this->assertTrue(course_request::can_request(context_system::instance()));
  6214. $this->assertTrue(course_request::can_request($context1));
  6215. $this->assertFalse(course_request::can_request($context2));
  6216. $this->assertTrue(course_request::can_request($context3));
  6217. // Disable course request functionality.
  6218. $CFG->enablecourserequests = false;
  6219. $this->assertFalse(course_request::can_request(context_system::instance()));
  6220. $this->assertFalse(course_request::can_request($context1));
  6221. $this->assertFalse(course_request::can_request($context2));
  6222. $this->assertFalse(course_request::can_request($context3));
  6223. }
  6224. /**
  6225. * Tests for the course_request::can_approve
  6226. */
  6227. public function test_can_approve_course_request() {
  6228. global $CFG;
  6229. $this->resetAfterTest();
  6230. $requestor = $this->getDataGenerator()->create_user();
  6231. $user = $this->getDataGenerator()->create_user();
  6232. $cat1 = $CFG->defaultrequestcategory;
  6233. $cat2 = $this->getDataGenerator()->create_category()->id;
  6234. $cat3 = $this->getDataGenerator()->create_category()->id;
  6235. // Enable course requests. Default 'user' role has capability to request courses.
  6236. $CFG->enablecourserequests = true;
  6237. $CFG->lockrequestcategory = 0;
  6238. $this->setUser($requestor);
  6239. $requestdata = ['summary_editor' => ['text' => '', 'format' => 0], 'name' => 'Req', 'reason' => 'test'];
  6240. $request1 = course_request::create((object)($requestdata));
  6241. $request2 = course_request::create((object)($requestdata + ['category' => $cat2]));
  6242. $request3 = course_request::create((object)($requestdata + ['category' => $cat3]));
  6243. $this->setUser($user);
  6244. // Add capability to approve courses.
  6245. $roleid = create_role('Test role', 'testrole', 'Test role description');
  6246. assign_capability('moodle/site:approvecourse', CAP_ALLOW, $roleid,
  6247. context_system::instance()->id, true);
  6248. role_assign($roleid, $user->id, context_coursecat::instance($cat2)->id);
  6249. accesslib_clear_all_caches_for_unit_testing();
  6250. $this->assertFalse($request1->can_approve());
  6251. $this->assertTrue($request2->can_approve());
  6252. $this->assertFalse($request3->can_approve());
  6253. // Delete category where course was requested. Now only site-wide manager can approve it.
  6254. core_course_category::get($cat2, MUST_EXIST, true)->delete_full(false);
  6255. $this->assertFalse($request2->can_approve());
  6256. $this->setAdminUser();
  6257. $this->assertTrue($request2->can_approve());
  6258. }
  6259. /**
  6260. * Test the course allowed module method.
  6261. */
  6262. public function test_course_allowed_module() {
  6263. $this->resetAfterTest();
  6264. global $DB;
  6265. $course = $this->getDataGenerator()->create_course();
  6266. $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
  6267. $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
  6268. $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
  6269. assign_capability('mod/assign:addinstance', CAP_PROHIBIT, $teacherrole->id, \context_course::instance($course->id));
  6270. // Global user (teacher) has no permissions in this course.
  6271. $this->setUser($teacher);
  6272. $this->assertFalse(course_allowed_module($course, 'assign'));
  6273. // Manager has permissions.
  6274. $this->assertTrue(course_allowed_module($course, 'assign', $manager));
  6275. }
  6276. /**
  6277. * Test the {@link average_number_of_participants()} function.
  6278. */
  6279. public function test_average_number_of_participants() {
  6280. global $DB;
  6281. $this->resetAfterTest(true);
  6282. $generator = $this->getDataGenerator();
  6283. $now = time();
  6284. // If there are no courses, expect zero number of participants per course.
  6285. $this->assertEquals(0, average_number_of_participants());
  6286. $c1 = $generator->create_course();
  6287. $c2 = $generator->create_course();
  6288. // If there are no users, expect zero number of participants per course.
  6289. $this->assertEquals(0, average_number_of_participants());
  6290. $t1 = $generator->create_user(['lastlogin' => $now]);
  6291. $s1 = $generator->create_user(['lastlogin' => $now]);
  6292. $s2 = $generator->create_user(['lastlogin' => $now - WEEKSECS]);
  6293. $s3 = $generator->create_user(['lastlogin' => $now - WEEKSECS]);
  6294. $s4 = $generator->create_user(['lastlogin' => $now - YEARSECS]);
  6295. // We have courses, we have users, but no enrolments yet.
  6296. $this->assertEquals(0, average_number_of_participants());
  6297. // Front page enrolments are ignored.
  6298. $generator->enrol_user($t1->id, SITEID, 'teacher');
  6299. $this->assertEquals(0, average_number_of_participants());
  6300. // The teacher enrolled into one of the two courses.
  6301. $generator->enrol_user($t1->id, $c1->id, 'editingteacher');
  6302. $this->assertEquals(0.5, average_number_of_participants());
  6303. // The teacher enrolled into both courses.
  6304. $generator->enrol_user($t1->id, $c2->id, 'editingteacher');
  6305. $this->assertEquals(1, average_number_of_participants());
  6306. // Student 1 enrolled in the Course 1 only.
  6307. $generator->enrol_user($s1->id, $c1->id, 'student');
  6308. $this->assertEquals(1.5, average_number_of_participants());
  6309. // Student 2 enrolled in both courses, but the enrolment in the Course 2 not active yet (enrolment starts in the future).
  6310. $generator->enrol_user($s2->id, $c1->id, 'student');
  6311. $generator->enrol_user($s2->id, $c2->id, 'student', 'manual', $now + WEEKSECS);
  6312. $this->assertEquals(2.5, average_number_of_participants());
  6313. $this->assertEquals(2, average_number_of_participants(true));
  6314. // Student 3 enrolled in the Course 1, but the enrolment already expired.
  6315. $generator->enrol_user($s3->id, $c1->id, 'student', 'manual', 0, $now - YEARSECS);
  6316. $this->assertEquals(3, average_number_of_participants());
  6317. $this->assertEquals(2, average_number_of_participants(true));
  6318. // Student 4 enrolled in both courses, but the enrolment has been suspended.
  6319. $generator->enrol_user($s4->id, $c1->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED);
  6320. $generator->enrol_user($s4->id, $c2->id, 'student', 'manual', $now - DAYSECS, $now + YEARSECS, ENROL_USER_SUSPENDED);
  6321. $this->assertEquals(4, average_number_of_participants());
  6322. $this->assertEquals(2, average_number_of_participants(true));
  6323. // Consider only t1 and s1 who logged in recently.
  6324. $this->assertEquals(1.5, average_number_of_participants(false, $now - DAYSECS));
  6325. // Consider only t1, s1, s2 and s3 who logged in in recent weeks.
  6326. $this->assertEquals(3, average_number_of_participants(false, $now - 4 * WEEKSECS));
  6327. // Hidden courses are excluded from stats.
  6328. $DB->set_field('course', 'visible', 0, ['id' => $c1->id]);
  6329. $this->assertEquals(3, average_number_of_participants());
  6330. $this->assertEquals(1, average_number_of_participants(true));
  6331. }
  6332. }