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