PageRenderTime 111ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 2ms

/course/tests/courselib_test.php

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