1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Unit tests for the core_notes implementation of the privacy API. 19 * 20 * @package core_notes 21 * @category test 22 * @copyright 2018 Zig Tan <zig@moodle.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 global $CFG; 28 29 require_once($CFG->dirroot . "/notes/lib.php"); 30 31 use \core_notes\privacy\provider; 32 use \core_privacy\local\request\writer; 33 use \core_privacy\local\request\approved_contextlist; 34 use \core_privacy\local\request\approved_userlist; 35 36 /** 37 * Unit tests for the core_notes implementation of the privacy API. 38 * 39 * @copyright 2018 Zig Tan <zig@moodle.com> 40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 41 */ 42 class core_notes_privacy_testcase extends \core_privacy\tests\provider_testcase { 43 44 /** 45 * Test for provider::get_contexts_for_userid(). 46 */ 47 public function test_get_contexts_for_userid() { 48 global $DB; 49 50 // Test setup. 51 $this->resetAfterTest(true); 52 $this->setAdminUser(); 53 set_config('enablenotes', true); 54 55 $teacher1 = $this->getDataGenerator()->create_user(); 56 $this->setUser($teacher1); 57 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 58 59 $student = $this->getDataGenerator()->create_user(); 60 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 61 62 // Create Courses, then enrol a teacher and a student. 63 $nocourses = 5; 64 $courses = []; 65 $coursecontextids = []; 66 for ($c = 1; $c <= $nocourses; $c++) { 67 $course = $this->getDataGenerator()->create_course(); 68 $coursecontext = context_course::instance($course->id); 69 70 role_assign($teacherrole->id, $teacher1->id, $coursecontext->id); 71 role_assign($studentrole->id, $student->id, $coursecontext->id); 72 73 // Only create private user notes (i.e. NOTES_STATE_DRAFT) for student in Course 1, 2, 3 written by the teacher. 74 if ($c <= 3) { 75 $this->help_create_user_note( 76 $student->id, 77 NOTES_STATE_DRAFT, 78 $course->id, 79 "Test private user note about the student in Course $c by the teacher" 80 ); 81 } 82 83 $courses[$c] = $course; 84 $coursecontextids[] = $coursecontext->id; 85 } 86 87 // Test Teacher 1's contexts equals 3 because only 3 user notes were added for Course 1, 2, and 3. 88 // Course 4 and 5 does not have any notes associated with it, so the contexts should not be returned. 89 $contexts = provider::get_contexts_for_userid($teacher1->id); 90 $this->assertCount(3, $contexts->get_contextids()); 91 92 // Test the Student's contexts is 0 because the notes written by the teacher are private. 93 $contexts = provider::get_contexts_for_userid($student->id); 94 $this->assertCount(0, $contexts->get_contextids()); 95 96 // Add a public user note (i.e. NOTES_STATE_PUBLIC) written by the Teacher about the Student in Course 4. 97 $course = $courses[4]; 98 $this->help_create_user_note( 99 $student->id, 100 NOTES_STATE_PUBLIC, 101 $course->id, 102 "Test public user note about the student in Course 4 by the teacher" 103 ); 104 105 // Test Teacher 1's contexts equals 4 after adding a public note about a student in Course 4. 106 $contexts = provider::get_contexts_for_userid($teacher1->id); 107 $this->assertCount(4, $contexts->get_contextids()); 108 109 // Test the Student's contexts is 1 for Course 4 because there is a public note written by the teacher. 110 $contexts = provider::get_contexts_for_userid($student->id); 111 $this->assertCount(1, $contexts->get_contextids()); 112 113 // Add a site-wide user note (i.e. NOTES_STATE_SITE) written by the Teacher 1 about the Student in Course 3. 114 $course = $courses[3]; 115 $this->help_create_user_note( 116 $student->id, 117 NOTES_STATE_SITE, 118 $course->id, 119 "Test site-wide user note about the student in Course 3 by the teacher" 120 ); 121 122 // Test the Student's contexts is 2 for Courses 3, 4 because there is a public and site-wide note written by the Teacher. 123 $contexts = provider::get_contexts_for_userid($student->id); 124 $this->assertCount(2, $contexts->get_contextids()); 125 126 // Add a site-wide user note for the Teacher 1 by another Teacher 2 in Course 5. 127 $teacher2 = $this->getDataGenerator()->create_user(); 128 $this->setUser($teacher2); 129 130 $course = $courses[5]; 131 $this->help_create_user_note( 132 $teacher1->id, 133 NOTES_STATE_SITE, 134 $course->id, 135 "Test site-wide user note about the teacher in Course 5 by another teacher" 136 ); 137 138 // Test Teacher 1's contexts equals 5 after adding the note from another teacher. 139 $contextlist = provider::get_contexts_for_userid($teacher1->id); 140 $this->assertCount(5, $contextlist->get_contextids()); 141 142 // Test Teacher 1's contexts match the contexts of the Courses associated with notes created. 143 $this->assertEmpty(array_diff($coursecontextids, $contextlist->get_contextids())); 144 } 145 146 /** 147 * Test for provider::export_user_data(). 148 */ 149 public function test_export_user_data() { 150 global $DB; 151 152 // Test setup. 153 $this->resetAfterTest(true); 154 $this->setAdminUser(); 155 set_config('enablenotes', true); 156 157 $teacher1 = $this->getDataGenerator()->create_user(); 158 $this->setUser($teacher1); 159 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 160 161 $nocourses = 5; 162 $nostudents = 2; 163 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 164 165 $courses = []; 166 $coursecontextids = []; 167 for ($c = 1; $c <= $nocourses; $c++) { 168 // Create a Course, then enrol a teacher and enrol 2 students. 169 $course = $this->getDataGenerator()->create_course(); 170 $coursecontext = context_course::instance($course->id); 171 172 role_assign($teacherrole->id, $teacher1->id, $coursecontext->id); 173 174 // Only create public user notes (i.e. NOTES_STATE_PUBLIC) for students in Course 1, 2, 3 written by the teacher. 175 if ($c <= 3) { 176 for ($s = 0; $s < $nostudents; $s++) { 177 $student = $this->getDataGenerator()->create_user(); 178 role_assign($studentrole->id, $student->id, $coursecontext->id); 179 180 // Create test public user note data written for students by the teacher. 181 $this->help_create_user_note( 182 $student->id, 183 NOTES_STATE_PUBLIC, 184 $course->id, 185 "Test public user note for student $s in Course $c by the teacher" 186 ); 187 } 188 // Store the Course context for those which have test notes added for verification. 189 $coursecontextids[] = $coursecontext->id; 190 } 191 192 $courses[$c] = $course; 193 } 194 195 // Add a site-wide user note for Teacher 1 by another Teacher 2 in Course 4. 196 $teacher2 = $this->getDataGenerator()->create_user(); 197 $this->setUser($teacher2); 198 199 $course = $courses[4]; 200 $this->help_create_user_note( 201 $teacher1->id, 202 NOTES_STATE_SITE, 203 $course->id, 204 "Test site-wide user note about the teacher in Course 4 by another teacher" 205 ); 206 // Store the Course context for those which have test notes added for verification. 207 $coursecontextids[] = context_course::instance($course->id)->id; 208 209 // Add a private user note for Teacher 1 by another Teacher 2 in Course 5. 210 $course = $courses[5]; 211 $this->help_create_user_note( 212 $teacher1->id, 213 NOTES_STATE_DRAFT, 214 $course->id, 215 "Test private user note about the teacher in Course 5 by another teacher" 216 ); 217 218 // Test the number of contexts returned matches the Course contexts created with notes. 219 $contextlist = provider::get_contexts_for_userid($teacher1->id); 220 $this->assertEmpty(array_diff($coursecontextids, $contextlist->get_contextids())); 221 222 $approvedcontextlist = new approved_contextlist($teacher1, 'core_notes', $contextlist->get_contextids()); 223 224 // Retrieve User notes created by the teacher. 225 provider::export_user_data($approvedcontextlist); 226 227 // Test the core_notes data is exported at the Course context level and has content. 228 foreach ($contextlist as $context) { 229 $this->assertEquals(CONTEXT_COURSE, $context->contextlevel); 230 231 $writer = writer::with_context($context); 232 $this->assertTrue($writer->has_any_data()); 233 } 234 } 235 236 /** 237 * Test for provider::delete_data_for_all_users_in_context(). 238 */ 239 public function test_delete_data_for_all_users_in_context() { 240 global $DB; 241 242 // Test setup. 243 $this->resetAfterTest(true); 244 $this->setAdminUser(); 245 set_config('enablenotes', true); 246 247 $teacher = $this->getDataGenerator()->create_user(); 248 $this->setUser($teacher); 249 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 250 251 $nocourses = 2; 252 $nostudents = 5; 253 $nonotes = 7; 254 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 255 256 $n = 0; 257 for ($c = 0; $c < $nocourses; $c++) { 258 // Create a Course, then enrol a teacher and enrol 2 students. 259 $course = $this->getDataGenerator()->create_course(); 260 $coursecontext = context_course::instance($course->id); 261 262 role_assign($teacherrole->id, $teacher->id, $coursecontext->id); 263 264 for ($s = 0; $s < $nostudents; $s++) { 265 if ($n < $nonotes) { 266 $student = $this->getDataGenerator()->create_user(); 267 role_assign($studentrole->id, $student->id, $coursecontext->id); 268 269 // Create test note data. 270 $this->help_create_user_note( 271 $student->id, 272 NOTES_STATE_PUBLIC, 273 $course->id, 274 "Test user note for student $s in Course $c" 275 ); 276 } 277 $n++; 278 } 279 } 280 281 // Test the number of contexts returned equals the number of Courses created with user notes for its students. 282 $contextlist = provider::get_contexts_for_userid($teacher->id); 283 $this->assertCount($nocourses, $contextlist->get_contextids()); 284 285 // Test the created user note records in mdl_post table matches the test number of user notes specified. 286 $notes = $DB->get_records('post', ['module' => 'notes', 'usermodified' => $teacher->id]); 287 $this->assertCount($nonotes, $notes); 288 289 // Delete all user note records in mdl_post table by the specified Course context. 290 foreach ($contextlist->get_contexts() as $context) { 291 provider::delete_data_for_all_users_in_context($context); 292 } 293 294 // Test the core_note records in mdl_post table is equals zero. 295 $notes = $DB->get_records('post', ['module' => 'notes', 'usermodified' => $teacher->id]); 296 $this->assertCount(0, $notes); 297 } 298 299 /** 300 * Test for provider::delete_data_for_user(). 301 */ 302 public function test_delete_data_for_user() { 303 global $DB; 304 305 // Test setup. 306 $this->resetAfterTest(true); 307 $this->setAdminUser(); 308 set_config('enablenotes', true); 309 310 $teacher = $this->getDataGenerator()->create_user(); 311 $this->setUser($teacher); 312 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 313 314 $nocourses = 2; 315 $nostudents = 5; 316 $nonotes = 7; 317 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 318 319 $n = 0; 320 for ($c = 0; $c < $nocourses; $c++) { 321 // Create a Course, then enrol a teacher and enrol 2 students. 322 $course = $this->getDataGenerator()->create_course(); 323 $coursecontext = context_course::instance($course->id); 324 325 role_assign($teacherrole->id, $teacher->id, $coursecontext->id); 326 327 for ($s = 0; $s < $nostudents; $s++) { 328 if ($n < $nonotes) { 329 $student = $this->getDataGenerator()->create_user(); 330 role_assign($studentrole->id, $student->id, $coursecontext->id); 331 332 // Create test note data. 333 $this->help_create_user_note( 334 $student->id, 335 NOTES_STATE_PUBLIC, 336 $course->id, 337 "Test user note for student $s in Course $c" 338 ); 339 } 340 $n++; 341 } 342 } 343 344 // Test the number of contexts returned equals the number of Courses created with user notes for its students. 345 $contextlist = provider::get_contexts_for_userid($teacher->id); 346 $this->assertCount($nocourses, $contextlist->get_contextids()); 347 348 // Test the created user note records in mdl_post table matches the test number of user notes specified. 349 $notes = $DB->get_records('post', ['module' => 'notes', 'usermodified' => $teacher->id]); 350 $this->assertCount($nonotes, $notes); 351 352 // Delete all user note records in mdl_post table created by the specified teacher. 353 $approvedcontextlist = new approved_contextlist($teacher, 'core_notes', $contextlist->get_contextids()); 354 provider::delete_data_for_user($approvedcontextlist); 355 356 // Test the core_note records in mdl_post table is equals zero. 357 $notes = $DB->get_records('post', ['module' => 'notes', 'usermodified' => $teacher->id]); 358 $this->assertCount(0, $notes); 359 } 360 361 /** 362 * Test that only users within a course context are fetched. 363 */ 364 public function test_get_users_in_context() { 365 global $DB; 366 367 $this->resetAfterTest(true); 368 369 $component = 'core_notes'; 370 // Test setup. 371 $this->setAdminUser(); 372 set_config('enablenotes', true); 373 // Create a teacher. 374 $teacher1 = $this->getDataGenerator()->create_user(); 375 $this->setUser($teacher1); 376 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 377 // Create a student. 378 $student = $this->getDataGenerator()->create_user(); 379 // Create student2. 380 $student2 = $this->getDataGenerator()->create_user(); 381 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 382 383 // Create courses, then enrol a teacher and a student. 384 $nocourses = 3; 385 for ($c = 1; $c <= $nocourses; $c++) { 386 ${'course' . $c} = $this->getDataGenerator()->create_course(); 387 ${'coursecontext' . $c} = context_course::instance(${'course' . $c}->id); 388 389 role_assign($teacherrole->id, $teacher1->id, ${'coursecontext' . $c}->id); 390 role_assign($studentrole->id, $student->id, ${'coursecontext' . $c}->id); 391 role_assign($studentrole->id, $student2->id, ${'coursecontext' . $c}->id); 392 } 393 // The list of users in coursecontext1 should be empty (related data still have not been created). 394 $userlist1 = new \core_privacy\local\request\userlist($coursecontext1, $component); 395 provider::get_users_in_context($userlist1); 396 $this->assertCount(0, $userlist1); 397 // The list of users in coursecontext2 should be empty (related data still have not been created). 398 $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component); 399 provider::get_users_in_context($userlist2); 400 $this->assertCount(0, $userlist2); 401 // The list of users in coursecontext3 should be empty (related data still have not been created). 402 $userlist3 = new \core_privacy\local\request\userlist($coursecontext3, $component); 403 provider::get_users_in_context($userlist3); 404 $this->assertCount(0, $userlist3); 405 406 // Create private user notes (i.e. NOTES_STATE_DRAFT) for student in course1 and course2 written by the teacher. 407 $this->help_create_user_note($student->id, NOTES_STATE_DRAFT, $course1->id, 408 "Test private user note about the student in Course 1 by the teacher"); 409 $this->help_create_user_note($student->id, NOTES_STATE_DRAFT, $course2->id, 410 "Test private user note about the student in Course 2 by the teacher"); 411 412 // The list of users in coursecontext1 should return one user (teacher1). 413 provider::get_users_in_context($userlist1); 414 $this->assertCount(1, $userlist1); 415 $this->assertTrue(in_array($teacher1->id, $userlist1->get_userids())); 416 // The list of users in coursecontext2 should return one user (teacher1). 417 provider::get_users_in_context($userlist2); 418 $this->assertCount(1, $userlist2); 419 $this->assertTrue(in_array($teacher1->id, $userlist2->get_userids())); 420 // The list of users in coursecontext3 should not return any users. 421 provider::get_users_in_context($userlist3); 422 $this->assertCount(0, $userlist3); 423 424 // Create public user note (i.e. NOTES_STATE_PUBLIC) for student in course3 written by the teacher. 425 $this->help_create_user_note($student->id, NOTES_STATE_PUBLIC, $course3->id, 426 "Test public user note about the student in Course 3 by the teacher"); 427 428 // The list of users in coursecontext3 should return 2 users (teacher and student). 429 provider::get_users_in_context($userlist3); 430 $this->assertCount(2, $userlist3); 431 $this->assertTrue(in_array($teacher1->id, $userlist3->get_userids())); 432 $this->assertTrue(in_array($student->id, $userlist3->get_userids())); 433 434 // Create site user note (i.e. NOTES_STATE_SITE) for student2 in course3 written by the teacher. 435 $this->help_create_user_note($student2->id, NOTES_STATE_SITE, $course3->id, 436 "Test site-wide user note about student2 in Course 3 by the teacher" 437 ); 438 439 // The list of users in coursecontext3 should return 3 users (teacher, student and student2). 440 provider::get_users_in_context($userlist3); 441 $this->assertCount(3, $userlist3); 442 $this->assertTrue(in_array($teacher1->id, $userlist3->get_userids())); 443 $this->assertTrue(in_array($student->id, $userlist3->get_userids())); 444 $this->assertTrue(in_array($student2->id, $userlist3->get_userids())); 445 446 // The list of users should not return any users in a different context than course context. 447 $contextsystem = context_system::instance(); 448 $userlist4 = new \core_privacy\local\request\userlist($contextsystem, $component); 449 provider::get_users_in_context($userlist4); 450 $this->assertCount(0, $userlist4); 451 } 452 453 /** 454 * Test that data for users in approved userlist is deleted. 455 */ 456 public function test_delete_data_for_users() { 457 global $DB; 458 459 $this->resetAfterTest(true); 460 461 $component = 'core_notes'; 462 // Test setup. 463 $this->setAdminUser(); 464 set_config('enablenotes', true); 465 // Create a teacher. 466 $teacher1 = $this->getDataGenerator()->create_user(); 467 $this->setUser($teacher1); 468 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 469 // Create a student. 470 $student = $this->getDataGenerator()->create_user(); 471 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 472 473 // Create Courses, then enrol a teacher and a student. 474 $nocourses = 3; 475 for ($c = 1; $c <= $nocourses; $c++) { 476 ${'course' . $c} = $this->getDataGenerator()->create_course(); 477 ${'coursecontext' . $c} = context_course::instance(${'course' . $c}->id); 478 479 role_assign($teacherrole->id, $teacher1->id, ${'coursecontext' . $c}->id); 480 role_assign($studentrole->id, $student->id, ${'coursecontext' . $c}->id); 481 } 482 483 // Create private notes for student in the course1 and course2 written by the teacher. 484 $this->help_create_user_note($student->id, NOTES_STATE_DRAFT, $course1->id, 485 "Test private user note about the student in Course 1 by the teacher"); 486 $this->help_create_user_note($student->id, NOTES_STATE_DRAFT, $course2->id, 487 "Test private user note about the student in Course 2 by the teacher"); 488 // Create public notes for student in the course3 written by the teacher. 489 $this->help_create_user_note($student->id, NOTES_STATE_PUBLIC, $course3->id, 490 "Test public user note about the student in Course 3 by the teacher"); 491 492 // The list of users in coursecontext1 should return one user (teacher1). 493 $userlist1 = new \core_privacy\local\request\userlist($coursecontext1, $component); 494 provider::get_users_in_context($userlist1); 495 $this->assertCount(1, $userlist1); 496 $this->assertTrue(in_array($teacher1->id, $userlist1->get_userids())); 497 // The list of users in coursecontext2 should return one user (teacher1). 498 $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component); 499 provider::get_users_in_context($userlist2); 500 $this->assertCount(1, $userlist2); 501 $this->assertTrue(in_array($teacher1->id, $userlist2->get_userids())); 502 // The list of users in coursecontext3 should return two users (teacher1 and student). 503 $userlist3 = new \core_privacy\local\request\userlist($coursecontext3, $component); 504 provider::get_users_in_context($userlist3); 505 $this->assertCount(2, $userlist3); 506 $this->assertTrue(in_array($teacher1->id, $userlist3->get_userids())); 507 $this->assertTrue(in_array($student->id, $userlist3->get_userids())); 508 509 $approvedlist = new approved_userlist($coursecontext3, $component, [$student->id]); 510 // Delete using delete_data_for_user. 511 provider::delete_data_for_users($approvedlist); 512 // Re-fetch users in the coursecontext3. 513 $userlist3 = new \core_privacy\local\request\userlist($coursecontext3, $component); 514 // The user data in coursecontext3 should not be removed. 515 provider::get_users_in_context($userlist3); 516 $this->assertCount(2, $userlist3); 517 $this->assertTrue(in_array($teacher1->id, $userlist3->get_userids())); 518 $this->assertTrue(in_array($student->id, $userlist3->get_userids())); 519 520 $approvedlist = new approved_userlist($coursecontext3, $component, [$teacher1->id]); 521 // Delete using delete_data_for_user. 522 provider::delete_data_for_users($approvedlist); 523 // Re-fetch users in the coursecontext3. 524 $userlist3 = new \core_privacy\local\request\userlist($coursecontext3, $component); 525 // The user data in coursecontext3 should be removed. 526 provider::get_users_in_context($userlist3); 527 $this->assertCount(0, $userlist3); 528 529 // Re-fetch users in the coursecontext1. 530 $userlist1 = new \core_privacy\local\request\userlist($coursecontext1, $component); 531 provider::get_users_in_context($userlist1); 532 $this->assertCount(1, $userlist1); 533 534 $approvedlist = new approved_userlist($coursecontext1, $component, [$student->id]); 535 // Delete using delete_data_for_user. 536 provider::delete_data_for_users($approvedlist); 537 // Re-fetch users in the coursecontext1. 538 $userlist3 = new \core_privacy\local\request\userlist($coursecontext1, $component); 539 // The user data in coursecontext1 should not be removed. 540 provider::get_users_in_context($userlist3); 541 $this->assertCount(1, $userlist3); 542 $this->assertTrue(in_array($teacher1->id, $userlist3->get_userids())); 543 544 $approvedlist = new approved_userlist($coursecontext1, $component, [$teacher1->id]); 545 // Delete using delete_data_for_user. 546 provider::delete_data_for_users($approvedlist); 547 // Re-fetch users in the coursecontext1. 548 $userlist3 = new \core_privacy\local\request\userlist($coursecontext1, $component); 549 // The user data in coursecontext1 should be removed. 550 provider::get_users_in_context($userlist3); 551 $this->assertCount(0, $userlist3); 552 553 // Re-fetch users in the coursecontext2. 554 $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component); 555 provider::get_users_in_context($userlist2); 556 $this->assertCount(1, $userlist2); 557 558 // The list of users should not return any users for contexts different than course context. 559 $systemcontext = context_system::instance(); 560 $userlist4 = new \core_privacy\local\request\userlist($systemcontext, $component); 561 provider::get_users_in_context($userlist4); 562 $this->assertCount(0, $userlist4); 563 } 564 565 /** 566 * Helper function to create user notes for testing. 567 * 568 * @param int $userid The ID of the User associated with the note. 569 * @param string $state The publish status 570 * @param int $courseid The ID of the Course associated with the note. 571 * @param string $content The note content. 572 */ 573 protected function help_create_user_note($userid, $state, $courseid, $content) { 574 $note = (object) [ 575 'userid' => $userid, 576 'publishstate' => $state, 577 'courseid' => $courseid, 578 'content' => $content, 579 ]; 580 note_save($note); 581 } 582 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body