Differences Between: [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace core_courseformat; 18 19 use core\event\course_module_updated; 20 use cm_info; 21 use section_info; 22 use stdClass; 23 use course_modinfo; 24 use moodle_exception; 25 use context_module; 26 use context_course; 27 28 /** 29 * Contains the core course state actions. 30 * 31 * The methods from this class should be executed via "core_courseformat_edit" web service. 32 * 33 * Each format plugin could extend this class to provide new actions to the editor. 34 * Extended classes should be locate in "format_XXX\course" namespace and 35 * extends core_courseformat\stateactions. 36 * 37 * @package core_courseformat 38 * @copyright 2021 Ferran Recio <ferran@moodle.com> 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 */ 41 class stateactions { 42 43 /** 44 * Move course modules to another location in the same course. 45 * 46 * @param stateupdates $updates the affected course elements track 47 * @param stdClass $course the course object 48 * @param int[] $ids the list of affected course module ids 49 * @param int $targetsectionid optional target section id 50 * @param int $targetcmid optional target cm id 51 */ 52 public function cm_move( 53 stateupdates $updates, 54 stdClass $course, 55 array $ids, 56 ?int $targetsectionid = null, 57 ?int $targetcmid = null 58 ): void { 59 // Validate target elements. 60 if (!$targetsectionid && !$targetcmid) { 61 throw new moodle_exception("Action cm_move requires targetsectionid or targetcmid"); 62 } 63 64 $this->validate_cms($course, $ids, __FUNCTION__, ['moodle/course:manageactivities']); 65 // The moveto_module function move elements before a specific target. 66 // To keep the order the movements must be done in descending order (last activity first). 67 $ids = $this->sort_cm_ids_by_course_position($course, $ids, true); 68 69 // Target cm has more priority than target section. 70 if (!empty($targetcmid)) { 71 $this->validate_cms($course, [$targetcmid], __FUNCTION__); 72 $targetcm = get_fast_modinfo($course)->get_cm($targetcmid); 73 $targetsectionid = $targetcm->section; 74 } else { 75 $this->validate_sections($course, [$targetsectionid], __FUNCTION__); 76 } 77 78 // The origin sections must be updated as well. 79 $originalsections = []; 80 81 $beforecmdid = $targetcmid; 82 foreach ($ids as $cmid) { 83 // An updated $modinfo is needed on every loop as activities list change. 84 $modinfo = get_fast_modinfo($course); 85 $cm = $modinfo->get_cm($cmid); 86 $currentsectionid = $cm->section; 87 $targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST); 88 $beforecm = (!empty($beforecmdid)) ? $modinfo->get_cm($beforecmdid) : null; 89 if ($beforecm === null || $beforecm->id != $cmid) { 90 moveto_module($cm, $targetsection, $beforecm); 91 } 92 $beforecmdid = $cm->id; 93 $updates->add_cm_put($cm->id); 94 if ($currentsectionid != $targetsectionid) { 95 $originalsections[$currentsectionid] = true; 96 } 97 // If some of the original sections are also target sections, we don't need to update them. 98 if (array_key_exists($targetsectionid, $originalsections)) { 99 unset($originalsections[$targetsectionid]); 100 } 101 } 102 103 // Use section_state to return the full affected section and activities updated state. 104 $this->cm_state($updates, $course, $ids, $targetsectionid, $targetcmid); 105 106 foreach (array_keys($originalsections) as $sectionid) { 107 $updates->add_section_put($sectionid); 108 } 109 } 110 111 /** 112 * Sort the cm ids list depending on the course position. 113 * 114 * Some actions like move should be done in an specific order. 115 * 116 * @param stdClass $course the course object 117 * @param int[] $cmids the array of section $ids 118 * @param bool $descending if the sort order must be descending instead of ascending 119 * @return int[] the array of section ids sorted by section number 120 */ 121 protected function sort_cm_ids_by_course_position( 122 stdClass $course, 123 array $cmids, 124 bool $descending = false 125 ): array { 126 $modinfo = get_fast_modinfo($course); 127 $cmlist = array_keys($modinfo->get_cms()); 128 $cmposition = []; 129 foreach ($cmids as $cmid) { 130 $cmposition[$cmid] = array_search($cmid, $cmlist); 131 } 132 $sorting = ($descending) ? -1 : 1; 133 $sortfunction = function ($acmid, $bcmid) use ($sorting, $cmposition) { 134 return ($cmposition[$acmid] <=> $cmposition[$bcmid]) * $sorting; 135 }; 136 usort($cmids, $sortfunction); 137 return $cmids; 138 } 139 140 /** 141 * Move course sections to another location in the same course. 142 * 143 * @param stateupdates $updates the affected course elements track 144 * @param stdClass $course the course object 145 * @param int[] $ids the list of affected course module ids 146 * @param int $targetsectionid optional target section id 147 * @param int $targetcmid optional target cm id 148 */ 149 public function section_move( 150 stateupdates $updates, 151 stdClass $course, 152 array $ids, 153 ?int $targetsectionid = null, 154 ?int $targetcmid = null 155 ): void { 156 // Validate target elements. 157 if (!$targetsectionid) { 158 throw new moodle_exception("Action cm_move requires targetsectionid"); 159 } 160 161 $this->validate_sections($course, $ids, __FUNCTION__); 162 163 $coursecontext = context_course::instance($course->id); 164 require_capability('moodle/course:movesections', $coursecontext); 165 166 $modinfo = get_fast_modinfo($course); 167 168 // Target section. 169 $this->validate_sections($course, [$targetsectionid], __FUNCTION__); 170 $targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST); 171 172 $affectedsections = [$targetsection->section => true]; 173 174 $sections = $this->get_section_info($modinfo, $ids); 175 foreach ($sections as $section) { 176 $affectedsections[$section->section] = true; 177 move_section_to($course, $section->section, $targetsection->section); 178 } 179 180 // Use section_state to return the section and activities updated state. 181 $this->section_state($updates, $course, $ids, $targetsectionid); 182 183 // All course sections can be renamed because of the resort. 184 $allsections = $modinfo->get_section_info_all(); 185 foreach ($allsections as $section) { 186 // Ignore the affected sections because they are already in the updates. 187 if (isset($affectedsections[$section->section])) { 188 continue; 189 } 190 $updates->add_section_put($section->id); 191 } 192 // The section order is at a course level. 193 $updates->add_course_put(); 194 } 195 196 /** 197 * Move course sections after to another location in the same course. 198 * 199 * @param stateupdates $updates the affected course elements track 200 * @param stdClass $course the course object 201 * @param int[] $ids the list of affected course module ids 202 * @param int $targetsectionid optional target section id 203 * @param int $targetcmid optional target cm id 204 */ 205 public function section_move_after( 206 stateupdates $updates, 207 stdClass $course, 208 array $ids, 209 ?int $targetsectionid = null, 210 ?int $targetcmid = null 211 ): void { 212 // Validate target elements. 213 if (!$targetsectionid) { 214 throw new moodle_exception("Action section_move_after requires targetsectionid"); 215 } 216 217 $this->validate_sections($course, $ids, __FUNCTION__); 218 219 $coursecontext = context_course::instance($course->id); 220 require_capability('moodle/course:movesections', $coursecontext); 221 222 // Section will move after the target section. This means it should be processed in 223 // descending order to keep the relative course order. 224 $this->validate_sections($course, [$targetsectionid], __FUNCTION__); 225 $ids = $this->sort_section_ids_by_section_number($course, $ids, true); 226 227 $format = course_get_format($course->id); 228 $affectedsections = [$targetsectionid => true]; 229 230 foreach ($ids as $id) { 231 // An update section_info is needed as section numbers can change on every section movement. 232 $modinfo = get_fast_modinfo($course); 233 $section = $modinfo->get_section_info_by_id($id, MUST_EXIST); 234 $targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST); 235 $affectedsections[$section->id] = true; 236 $format->move_section_after($section, $targetsection); 237 } 238 239 // Use section_state to return the section and activities updated state. 240 $this->section_state($updates, $course, $ids, $targetsectionid); 241 242 // All course sections can be renamed because of the resort. 243 $modinfo = get_fast_modinfo($course); 244 $allsections = $modinfo->get_section_info_all(); 245 foreach ($allsections as $section) { 246 // Ignore the affected sections because they are already in the updates. 247 if (isset($affectedsections[$section->id])) { 248 continue; 249 } 250 $updates->add_section_put($section->id); 251 } 252 // The section order is at a course level. 253 $updates->add_course_put(); 254 } 255 256 /** 257 * Sort the sections ids depending on the section number. 258 * 259 * Some actions like move should be done in an specific order. 260 * 261 * @param stdClass $course the course object 262 * @param int[] $sectionids the array of section $ids 263 * @param bool $descending if the sort order must be descending instead of ascending 264 * @return int[] the array of section ids sorted by section number 265 */ 266 protected function sort_section_ids_by_section_number( 267 stdClass $course, 268 array $sectionids, 269 bool $descending = false 270 ): array { 271 $sorting = ($descending) ? -1 : 1; 272 $sortfunction = function ($asection, $bsection) use ($sorting) { 273 return ($asection->section <=> $bsection->section) * $sorting; 274 }; 275 $modinfo = get_fast_modinfo($course); 276 $sections = $this->get_section_info($modinfo, $sectionids); 277 uasort($sections, $sortfunction); 278 return array_keys($sections); 279 } 280 281 /** 282 * Create a course section. 283 * 284 * This method follows the same logic as changenumsections.php. 285 * 286 * @param stateupdates $updates the affected course elements track 287 * @param stdClass $course the course object 288 * @param int[] $ids not used 289 * @param int $targetsectionid optional target section id (if not passed section will be appended) 290 * @param int $targetcmid not used 291 */ 292 public function section_add( 293 stateupdates $updates, 294 stdClass $course, 295 array $ids = [], 296 ?int $targetsectionid = null, 297 ?int $targetcmid = null 298 ): void { 299 300 $coursecontext = context_course::instance($course->id); 301 require_capability('moodle/course:update', $coursecontext); 302 303 // Get course format settings. 304 $format = course_get_format($course->id); 305 $lastsectionnumber = $format->get_last_section_number(); 306 $maxsections = $format->get_max_sections(); 307 308 if ($lastsectionnumber >= $maxsections) { 309 throw new moodle_exception('maxsectionslimit', 'moodle', $maxsections); 310 } 311 312 $modinfo = get_fast_modinfo($course); 313 314 // Get target section. 315 if ($targetsectionid) { 316 $this->validate_sections($course, [$targetsectionid], __FUNCTION__); 317 $targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST); 318 // Inserting sections at any position except in the very end requires capability to move sections. 319 require_capability('moodle/course:movesections', $coursecontext); 320 $insertposition = $targetsection->section + 1; 321 } else { 322 // Get last section. 323 $insertposition = 0; 324 } 325 326 course_create_section($course, $insertposition); 327 328 // Adding a section affects the full course structure. 329 $this->course_state($updates, $course); 330 } 331 332 /** 333 * Delete course sections. 334 * 335 * This method follows the same logic as editsection.php. 336 * 337 * @param stateupdates $updates the affected course elements track 338 * @param stdClass $course the course object 339 * @param int[] $ids section ids 340 * @param int $targetsectionid not used 341 * @param int $targetcmid not used 342 */ 343 public function section_delete( 344 stateupdates $updates, 345 stdClass $course, 346 array $ids = [], 347 ?int $targetsectionid = null, 348 ?int $targetcmid = null 349 ): void { 350 351 $coursecontext = context_course::instance($course->id); 352 require_capability('moodle/course:update', $coursecontext); 353 require_capability('moodle/course:movesections', $coursecontext); 354 355 foreach ($ids as $sectionid) { 356 // We need to get the latest modinfo on each iteration because the section numbers change. 357 $modinfo = get_fast_modinfo($course); 358 $section = $modinfo->get_section_info_by_id($sectionid, MUST_EXIST); 359 // Send all activity deletions. 360 if (!empty($modinfo->sections[$section->section])) { 361 foreach ($modinfo->sections[$section->section] as $modnumber) { 362 $cm = $modinfo->cms[$modnumber]; 363 $updates->add_cm_remove($cm->id); 364 } 365 } 366 course_delete_section($course, $section, true, true); 367 $updates->add_section_remove($sectionid); 368 } 369 370 // Removing a section affects the full course structure. 371 $this->course_state($updates, $course); 372 } 373 374 /** 375 * Hide course sections. 376 * 377 * @param stateupdates $updates the affected course elements track 378 * @param stdClass $course the course object 379 * @param int[] $ids section ids 380 * @param int $targetsectionid not used 381 * @param int $targetcmid not used 382 */ 383 public function section_hide( 384 stateupdates $updates, 385 stdClass $course, 386 array $ids = [], 387 ?int $targetsectionid = null, 388 ?int $targetcmid = null 389 ): void { 390 $this->set_section_visibility($updates, $course, $ids, 0); 391 } 392 393 /** 394 * Show course sections. 395 * 396 * @param stateupdates $updates the affected course elements track 397 * @param stdClass $course the course object 398 * @param int[] $ids section ids 399 * @param int $targetsectionid not used 400 * @param int $targetcmid not used 401 */ 402 public function section_show( 403 stateupdates $updates, 404 stdClass $course, 405 array $ids = [], 406 ?int $targetsectionid = null, 407 ?int $targetcmid = null 408 ): void { 409 $this->set_section_visibility($updates, $course, $ids, 1); 410 } 411 412 /** 413 * Show course sections. 414 * 415 * @param stateupdates $updates the affected course elements track 416 * @param stdClass $course the course object 417 * @param int[] $ids section ids 418 * @param int $visible the new visible value 419 */ 420 protected function set_section_visibility ( 421 stateupdates $updates, 422 stdClass $course, 423 array $ids, 424 int $visible 425 ) { 426 $this->validate_sections($course, $ids, __FUNCTION__); 427 $coursecontext = context_course::instance($course->id); 428 require_all_capabilities(['moodle/course:update', 'moodle/course:sectionvisibility'], $coursecontext); 429 430 $modinfo = get_fast_modinfo($course); 431 432 foreach ($ids as $sectionid) { 433 $section = $modinfo->get_section_info_by_id($sectionid, MUST_EXIST); 434 course_update_section($course, $section, ['visible' => $visible]); 435 } 436 $this->section_state($updates, $course, $ids); 437 } 438 439 /** 440 * Show course cms. 441 * 442 * @param stateupdates $updates the affected course elements track 443 * @param stdClass $course the course object 444 * @param int[] $ids cm ids 445 * @param int $targetsectionid not used 446 * @param int $targetcmid not used 447 */ 448 public function cm_show( 449 stateupdates $updates, 450 stdClass $course, 451 array $ids = [], 452 ?int $targetsectionid = null, 453 ?int $targetcmid = null 454 ): void { 455 $this->set_cm_visibility($updates, $course, $ids, 1, 1); 456 } 457 458 /** 459 * Hide course cms. 460 * 461 * @param stateupdates $updates the affected course elements track 462 * @param stdClass $course the course object 463 * @param int[] $ids cm ids 464 * @param int $targetsectionid not used 465 * @param int $targetcmid not used 466 */ 467 public function cm_hide( 468 stateupdates $updates, 469 stdClass $course, 470 array $ids = [], 471 ?int $targetsectionid = null, 472 ?int $targetcmid = null 473 ): void { 474 $this->set_cm_visibility($updates, $course, $ids, 0, 1); 475 } 476 477 /** 478 * Stealth course cms. 479 * 480 * @param stateupdates $updates the affected course elements track 481 * @param stdClass $course the course object 482 * @param int[] $ids cm ids 483 * @param int $targetsectionid not used 484 * @param int $targetcmid not used 485 */ 486 public function cm_stealth( 487 stateupdates $updates, 488 stdClass $course, 489 array $ids = [], 490 ?int $targetsectionid = null, 491 ?int $targetcmid = null 492 ): void { 493 $this->set_cm_visibility($updates, $course, $ids, 1, 0); 494 } 495 496 /** 497 * Internal method to define the cm visibility. 498 * 499 * @param stateupdates $updates the affected course elements track 500 * @param stdClass $course the course object 501 * @param int[] $ids cm ids 502 * @param int $visible the new visible value 503 * @param int $coursevisible the new course visible value 504 */ 505 protected function set_cm_visibility( 506 stateupdates $updates, 507 stdClass $course, 508 array $ids, 509 int $visible, 510 int $coursevisible 511 ): void { 512 global $CFG; 513 514 $this->validate_cms( 515 $course, 516 $ids, 517 __FUNCTION__, 518 ['moodle/course:manageactivities', 'moodle/course:activityvisibility'] 519 ); 520 521 $format = course_get_format($course->id); 522 $modinfo = get_fast_modinfo($course); 523 524 $cms = $this->get_cm_info($modinfo, $ids); 525 foreach ($cms as $cm) { 526 // Check stealth availability. 527 if (!$coursevisible) { 528 $section = $cm->get_section_info(); 529 $allowstealth = !empty($CFG->allowstealth) && $format->allow_stealth_module_visibility($cm, $section); 530 $coursevisible = ($allowstealth) ? 0 : 1; 531 } 532 set_coursemodule_visible($cm->id, $visible, $coursevisible, false); 533 $modcontext = context_module::instance($cm->id); 534 course_module_updated::create_from_cm($cm, $modcontext)->trigger(); 535 } 536 course_modinfo::purge_course_modules_cache($course->id, $ids); 537 rebuild_course_cache($course->id, false, true); 538 539 foreach ($cms as $cm) { 540 $updates->add_cm_put($cm->id); 541 } 542 } 543 544 /** 545 * Duplicate a course modules instances into the same course. 546 * 547 * @param stateupdates $updates the affected course elements track 548 * @param stdClass $course the course object 549 * @param int[] $ids course modules ids to duplicate 550 * @param int|null $targetsectionid optional target section id destination 551 * @param int|null $targetcmid optional target before cm id destination 552 */ 553 public function cm_duplicate( 554 stateupdates $updates, 555 stdClass $course, 556 array $ids = [], 557 ?int $targetsectionid = null, 558 ?int $targetcmid = null 559 ): void { 560 $this->validate_cms( 561 $course, 562 $ids, 563 __FUNCTION__, 564 ['moodle/course:manageactivities', 'moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport'] 565 ); 566 567 $modinfo = get_fast_modinfo($course); 568 $cms = $this->get_cm_info($modinfo, $ids); 569 570 // Check capabilities on every activity context. 571 foreach ($cms as $cm) { 572 if (!course_allowed_module($course, $cm->modname)) { 573 throw new moodle_exception('No permission to create that activity'); 574 } 575 } 576 577 $targetsection = null; 578 if (!empty($targetsectionid)) { 579 $this->validate_sections($course, [$targetsectionid], __FUNCTION__); 580 $targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST); 581 } 582 583 $beforecm = null; 584 if (!empty($targetcmid)) { 585 $this->validate_cms($course, [$targetcmid], __FUNCTION__); 586 $beforecm = $modinfo->get_cm($targetcmid); 587 $targetsection = $modinfo->get_section_info_by_id($beforecm->section, MUST_EXIST); 588 } 589 590 // Duplicate course modules. 591 $affectedcmids = []; 592 foreach ($cms as $cm) { 593 if ($newcm = duplicate_module($course, $cm)) { 594 if ($targetsection) { 595 moveto_module($newcm, $targetsection, $beforecm); 596 } else { 597 $affectedcmids[] = $newcm->id; 598 } 599 } 600 } 601 602 if ($targetsection) { 603 $this->section_state($updates, $course, [$targetsection->id]); 604 } else { 605 $this->cm_state($updates, $course, $affectedcmids); 606 } 607 } 608 609 /** 610 * Delete course cms. 611 * 612 * @param stateupdates $updates the affected course elements track 613 * @param stdClass $course the course object 614 * @param int[] $ids section ids 615 * @param int $targetsectionid not used 616 * @param int $targetcmid not used 617 */ 618 public function cm_delete( 619 stateupdates $updates, 620 stdClass $course, 621 array $ids = [], 622 ?int $targetsectionid = null, 623 ?int $targetcmid = null 624 ): void { 625 626 $this->validate_cms($course, $ids, __FUNCTION__, ['moodle/course:manageactivities']); 627 628 $format = course_get_format($course->id); 629 $modinfo = get_fast_modinfo($course); 630 $affectedsections = []; 631 632 $cms = $this->get_cm_info($modinfo, $ids); 633 foreach ($cms as $cm) { 634 $section = $cm->get_section_info(); 635 $affectedsections[$section->id] = $section; 636 $format->delete_module($cm, true); 637 $updates->add_cm_remove($cm->id); 638 } 639 640 foreach ($affectedsections as $sectionid => $section) { 641 $updates->add_section_put($sectionid); 642 } 643 } 644 645 /** 646 * Move course cms to the right. Indent = 1. 647 * 648 * @param stateupdates $updates the affected course elements track 649 * @param stdClass $course the course object 650 * @param int[] $ids cm ids 651 * @param int $targetsectionid not used 652 * @param int $targetcmid not used 653 */ 654 public function cm_moveright( 655 stateupdates $updates, 656 stdClass $course, 657 array $ids = [], 658 ?int $targetsectionid = null, 659 ?int $targetcmid = null 660 ): void { 661 $this->set_cm_indentation($updates, $course, $ids, 1); 662 } 663 664 /** 665 * Move course cms to the left. Indent = 0. 666 * 667 * @param stateupdates $updates the affected course elements track 668 * @param stdClass $course the course object 669 * @param int[] $ids cm ids 670 * @param int $targetsectionid not used 671 * @param int $targetcmid not used 672 */ 673 public function cm_moveleft( 674 stateupdates $updates, 675 stdClass $course, 676 array $ids = [], 677 ?int $targetsectionid = null, 678 ?int $targetcmid = null 679 ): void { 680 $this->set_cm_indentation($updates, $course, $ids, 0); 681 } 682 683 /** 684 * Internal method to define the cm indentation level. 685 * 686 * @param stateupdates $updates the affected course elements track 687 * @param stdClass $course the course object 688 * @param int[] $ids cm ids 689 * @param int $indent new value for indentation 690 */ 691 protected function set_cm_indentation( 692 stateupdates $updates, 693 stdClass $course, 694 array $ids, 695 int $indent 696 ): void { 697 global $DB; 698 699 $this->validate_cms($course, $ids, __FUNCTION__, ['moodle/course:manageactivities']); 700 $modinfo = get_fast_modinfo($course); 701 $cms = $this->get_cm_info($modinfo, $ids); 702 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($cms), SQL_PARAMS_NAMED); 703 $DB->set_field_select('course_modules', 'indent', $indent, "id $insql", $inparams); 704 rebuild_course_cache($course->id, false, true); 705 foreach ($cms as $cm) { 706 $modcontext = context_module::instance($cm->id); 707 course_module_updated::create_from_cm($cm, $modcontext)->trigger(); 708 $updates->add_cm_put($cm->id); 709 } 710 } 711 712 /** 713 * Set NOGROUPS const value to cms groupmode. 714 * 715 * @param stateupdates $updates the affected course elements track 716 * @param stdClass $course the course object 717 * @param int[] $ids cm ids 718 * @param int $targetsectionid not used 719 * @param int $targetcmid not used 720 */ 721 public function cm_nogroups( 722 stateupdates $updates, 723 stdClass $course, 724 array $ids = [], 725 ?int $targetsectionid = null, 726 ?int $targetcmid = null 727 ): void { 728 $this->set_cm_groupmode($updates, $course, $ids, NOGROUPS); 729 } 730 731 /** 732 * Set VISIBLEGROUPS const value to cms groupmode. 733 * 734 * @param stateupdates $updates the affected course elements track 735 * @param stdClass $course the course object 736 * @param int[] $ids cm ids 737 * @param int $targetsectionid not used 738 * @param int $targetcmid not used 739 */ 740 public function cm_visiblegroups( 741 stateupdates $updates, 742 stdClass $course, 743 array $ids = [], 744 ?int $targetsectionid = null, 745 ?int $targetcmid = null 746 ): void { 747 $this->set_cm_groupmode($updates, $course, $ids, VISIBLEGROUPS); 748 } 749 750 /** 751 * Set SEPARATEGROUPS const value to cms groupmode. 752 * 753 * @param stateupdates $updates the affected course elements track 754 * @param stdClass $course the course object 755 * @param int[] $ids cm ids 756 * @param int $targetsectionid not used 757 * @param int $targetcmid not used 758 */ 759 public function cm_separategroups( 760 stateupdates $updates, 761 stdClass $course, 762 array $ids = [], 763 ?int $targetsectionid = null, 764 ?int $targetcmid = null 765 ): void { 766 $this->set_cm_groupmode($updates, $course, $ids, SEPARATEGROUPS); 767 } 768 769 /** 770 * Internal method to define the cm groupmode value. 771 * 772 * @param stateupdates $updates the affected course elements track 773 * @param stdClass $course the course object 774 * @param int[] $ids cm ids 775 * @param int $groupmode new value for groupmode: NOGROUPS, SEPARATEGROUPS, VISIBLEGROUPS 776 */ 777 protected function set_cm_groupmode( 778 stateupdates $updates, 779 stdClass $course, 780 array $ids, 781 int $groupmode 782 ): void { 783 global $DB; 784 785 $this->validate_cms($course, $ids, __FUNCTION__, ['moodle/course:manageactivities']); 786 $modinfo = get_fast_modinfo($course); 787 $cms = $this->get_cm_info($modinfo, $ids); 788 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($cms), SQL_PARAMS_NAMED); 789 $DB->set_field_select('course_modules', 'groupmode', $groupmode, "id $insql", $inparams); 790 rebuild_course_cache($course->id, false, true); 791 foreach ($cms as $cm) { 792 $modcontext = context_module::instance($cm->id); 793 course_module_updated::create_from_cm($cm, $modcontext)->trigger(); 794 $updates->add_cm_put($cm->id); 795 } 796 } 797 798 /** 799 * Extract several cm_info from the course_modinfo. 800 * 801 * @param course_modinfo $modinfo the course modinfo. 802 * @param int[] $ids the course modules $ids 803 * @return cm_info[] the extracted cm_info objects 804 */ 805 protected function get_cm_info (course_modinfo $modinfo, array $ids): array { 806 $cms = []; 807 foreach ($ids as $cmid) { 808 $cms[$cmid] = $modinfo->get_cm($cmid); 809 } 810 return $cms; 811 } 812 813 /** 814 * Extract several section_info from the course_modinfo. 815 * 816 * @param course_modinfo $modinfo the course modinfo. 817 * @param int[] $ids the course modules $ids 818 * @return section_info[] the extracted section_info objects 819 */ 820 protected function get_section_info(course_modinfo $modinfo, array $ids): array { 821 $sections = []; 822 foreach ($ids as $sectionid) { 823 $sections[$sectionid] = $modinfo->get_section_info_by_id($sectionid); 824 } 825 return $sections; 826 } 827 828 /** 829 * Update the course content section collapsed value. 830 * 831 * @param stateupdates $updates the affected course elements track 832 * @param stdClass $course the course object 833 * @param int[] $ids the collapsed section ids 834 * @param int $targetsectionid not used 835 * @param int $targetcmid not used 836 */ 837 public function section_content_collapsed( 838 stateupdates $updates, 839 stdClass $course, 840 array $ids = [], 841 ?int $targetsectionid = null, 842 ?int $targetcmid = null 843 ): void { 844 if (!empty($ids)) { 845 $this->validate_sections($course, $ids, __FUNCTION__); 846 } 847 $format = course_get_format($course->id); 848 $format->set_sections_preference('contentcollapsed', $ids); 849 } 850 851 /** 852 * Update the course index section collapsed value. 853 * 854 * @param stateupdates $updates the affected course elements track 855 * @param stdClass $course the course object 856 * @param int[] $ids the collapsed section ids 857 * @param int $targetsectionid not used 858 * @param int $targetcmid not used 859 */ 860 public function section_index_collapsed( 861 stateupdates $updates, 862 stdClass $course, 863 array $ids = [], 864 ?int $targetsectionid = null, 865 ?int $targetcmid = null 866 ): void { 867 if (!empty($ids)) { 868 $this->validate_sections($course, $ids, __FUNCTION__); 869 } 870 $format = course_get_format($course->id); 871 $format->set_sections_preference('indexcollapsed', $ids); 872 } 873 874 /** 875 * Add the update messages of the updated version of any cm and section related to the cm ids. 876 * 877 * This action is mainly used by legacy actions to partially update the course state when the 878 * result of core_course_edit_module is not enough to generate the correct state data. 879 * 880 * @param stateupdates $updates the affected course elements track 881 * @param stdClass $course the course object 882 * @param int[] $ids the list of affected course module ids 883 * @param int $targetsectionid optional target section id 884 * @param int $targetcmid optional target cm id 885 */ 886 public function cm_state( 887 stateupdates $updates, 888 stdClass $course, 889 array $ids, 890 ?int $targetsectionid = null, 891 ?int $targetcmid = null 892 ): void { 893 894 // Collect all section and cm to return. 895 $cmids = []; 896 foreach ($ids as $cmid) { 897 $cmids[$cmid] = true; 898 } 899 if ($targetcmid) { 900 $cmids[$targetcmid] = true; 901 } 902 903 $sectionids = []; 904 if ($targetsectionid) { 905 $this->validate_sections($course, [$targetsectionid], __FUNCTION__); 906 $sectionids[$targetsectionid] = true; 907 } 908 909 $this->validate_cms($course, array_keys($cmids), __FUNCTION__); 910 911 $modinfo = course_modinfo::instance($course); 912 913 foreach (array_keys($cmids) as $cmid) { 914 915 // Add this action to updates array. 916 $updates->add_cm_put($cmid); 917 918 $cm = $modinfo->get_cm($cmid); 919 $sectionids[$cm->section] = true; 920 } 921 922 foreach (array_keys($sectionids) as $sectionid) { 923 $updates->add_section_put($sectionid); 924 } 925 } 926 927 /** 928 * Add the update messages of the updated version of any cm and section related to the section ids. 929 * 930 * This action is mainly used by legacy actions to partially update the course state when the 931 * result of core_course_edit_module is not enough to generate the correct state data. 932 * 933 * @param stateupdates $updates the affected course elements track 934 * @param stdClass $course the course object 935 * @param int[] $ids the list of affected course section ids 936 * @param int $targetsectionid optional target section id 937 * @param int $targetcmid optional target cm id 938 */ 939 public function section_state( 940 stateupdates $updates, 941 stdClass $course, 942 array $ids, 943 ?int $targetsectionid = null, 944 ?int $targetcmid = null 945 ): void { 946 947 $cmids = []; 948 if ($targetcmid) { 949 $this->validate_cms($course, [$targetcmid], __FUNCTION__); 950 $cmids[$targetcmid] = true; 951 } 952 953 $sectionids = []; 954 foreach ($ids as $sectionid) { 955 $sectionids[$sectionid] = true; 956 } 957 if ($targetsectionid) { 958 $sectionids[$targetsectionid] = true; 959 } 960 961 $this->validate_sections($course, array_keys($sectionids), __FUNCTION__); 962 963 $modinfo = course_modinfo::instance($course); 964 965 foreach (array_keys($sectionids) as $sectionid) { 966 $sectioninfo = $modinfo->get_section_info_by_id($sectionid); 967 $updates->add_section_put($sectionid); 968 // Add cms. 969 if (empty($modinfo->sections[$sectioninfo->section])) { 970 continue; 971 } 972 973 foreach ($modinfo->sections[$sectioninfo->section] as $modnumber) { 974 $mod = $modinfo->cms[$modnumber]; 975 if ($mod->is_visible_on_course_page()) { 976 $cmids[$mod->id] = true; 977 } 978 } 979 } 980 981 foreach (array_keys($cmids) as $cmid) { 982 // Add this action to updates array. 983 $updates->add_cm_put($cmid); 984 } 985 } 986 987 /** 988 * Add all the update messages from the complete course state. 989 * 990 * This action is mainly used by legacy actions to partially update the course state when the 991 * result of core_course_edit_module is not enough to generate the correct state data. 992 * 993 * @param stateupdates $updates the affected course elements track 994 * @param stdClass $course the course object 995 * @param int[] $ids the list of affected course module ids (not used) 996 * @param int $targetsectionid optional target section id (not used) 997 * @param int $targetcmid optional target cm id (not used) 998 */ 999 public function course_state( 1000 stateupdates $updates, 1001 stdClass $course, 1002 array $ids = [], 1003 ?int $targetsectionid = null, 1004 ?int $targetcmid = null 1005 ): void { 1006 1007 $modinfo = course_modinfo::instance($course); 1008 1009 $updates->add_course_put(); 1010 1011 // Add sections updates. 1012 $sections = $modinfo->get_section_info_all(); 1013 $sectionids = []; 1014 foreach ($sections as $sectioninfo) { 1015 $sectionids[] = $sectioninfo->id; 1016 } 1017 if (!empty($sectionids)) { 1018 $this->section_state($updates, $course, $sectionids); 1019 } 1020 } 1021 1022 /** 1023 * Checks related to sections: course format support them, all given sections exist and topic 0 is not included. 1024 * 1025 * @param stdClass $course The course where given $sectionids belong. 1026 * @param array $sectionids List of sections to validate. 1027 * @param string|null $info additional information in case of error (default null). 1028 * @throws moodle_exception if any id is not valid 1029 */ 1030 protected function validate_sections(stdClass $course, array $sectionids, ?string $info = null): void { 1031 global $DB; 1032 1033 if (empty($sectionids)) { 1034 throw new moodle_exception('emptysectionids', 'core', null, $info); 1035 } 1036 1037 // No section actions are allowed if course format does not support sections. 1038 $courseformat = course_get_format($course->id); 1039 if (!$courseformat->uses_sections()) { 1040 throw new moodle_exception('sectionactionnotsupported', 'core', null, $info); 1041 } 1042 1043 list($insql, $inparams) = $DB->get_in_or_equal($sectionids, SQL_PARAMS_NAMED); 1044 1045 // Check if all the given sections exist. 1046 $couintsections = $DB->count_records_select('course_sections', "id $insql", $inparams); 1047 if ($couintsections != count($sectionids)) { 1048 throw new moodle_exception('unexistingsectionid', 'core', null, $info); 1049 } 1050 } 1051 1052 /** 1053 * Checks related to course modules: all given cm exist and the user has the required capabilities. 1054 * 1055 * @param stdClass $course The course where given $cmids belong. 1056 * @param array $cmids List of course module ids to validate. 1057 * @param string $info additional information in case of error. 1058 * @param array $capabilities optional capabilities checks per each cm context. 1059 * @throws moodle_exception if any id is not valid 1060 */ 1061 protected function validate_cms(stdClass $course, array $cmids, ?string $info = null, array $capabilities = []): void { 1062 1063 if (empty($cmids)) { 1064 throw new moodle_exception('emptycmids', 'core', null, $info); 1065 } 1066 1067 $moduleinfo = get_fast_modinfo($course->id); 1068 $intersect = array_intersect($cmids, array_keys($moduleinfo->get_cms())); 1069 if (count($cmids) != count($intersect)) { 1070 throw new moodle_exception('unexistingcmid', 'core', null, $info); 1071 } 1072 if (!empty($capabilities)) { 1073 foreach ($cmids as $cmid) { 1074 $modcontext = context_module::instance($cmid); 1075 require_all_capabilities($capabilities, $modcontext); 1076 } 1077 } 1078 } 1079 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body