Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 400 and 401] [Versions 401 and 402] [Versions 401 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__);
  66  
  67          // Check capabilities on every activity context.
  68          foreach ($ids as $cmid) {
  69              $modcontext = context_module::instance($cmid);
  70              require_capability('moodle/course:manageactivities', $modcontext);
  71          }
  72  
  73          $modinfo = get_fast_modinfo($course);
  74  
  75          // Target cm has more priority than target section.
  76          if (!empty($targetcmid)) {
  77              $this->validate_cms($course, [$targetcmid], __FUNCTION__);
  78              $targetcm = $modinfo->get_cm($targetcmid);
  79              $targetsection = $modinfo->get_section_info_by_id($targetcm->section, MUST_EXIST);
  80          } else {
  81              $this->validate_sections($course, [$targetsectionid], __FUNCTION__);
  82              $targetcm = null;
  83              $targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST);
  84          }
  85  
  86          // The origin sections must be updated as well.
  87          $originalsections = [];
  88  
  89          $cms = $this->get_cm_info($modinfo, $ids);
  90          foreach ($cms as $cm) {
  91              $currentsection = $modinfo->get_section_info_by_id($cm->section, MUST_EXIST);
  92              moveto_module($cm, $targetsection, $targetcm);
  93              $updates->add_cm_put($cm->id);
  94              if ($currentsection->id != $targetsection->id) {
  95                  $originalsections[$currentsection->id] = 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($targetsection->id, $originalsections)) {
  99                  unset($originalsections[$targetsection->id]);
 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       * Move course sections to another location in the same course.
 113       *
 114       * @param stateupdates $updates the affected course elements track
 115       * @param stdClass $course the course object
 116       * @param int[] $ids the list of affected course module ids
 117       * @param int $targetsectionid optional target section id
 118       * @param int $targetcmid optional target cm id
 119       */
 120      public function section_move(
 121          stateupdates $updates,
 122          stdClass $course,
 123          array $ids,
 124          ?int $targetsectionid = null,
 125          ?int $targetcmid = null
 126      ): void {
 127          // Validate target elements.
 128          if (!$targetsectionid) {
 129              throw new moodle_exception("Action cm_move requires targetsectionid");
 130          }
 131  
 132          $this->validate_sections($course, $ids, __FUNCTION__);
 133  
 134          $coursecontext = context_course::instance($course->id);
 135          require_capability('moodle/course:movesections', $coursecontext);
 136  
 137          $modinfo = get_fast_modinfo($course);
 138  
 139          // Target section.
 140          $this->validate_sections($course, [$targetsectionid], __FUNCTION__);
 141          $targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST);
 142  
 143          $affectedsections = [$targetsection->section => true];
 144  
 145          $sections = $this->get_section_info($modinfo, $ids);
 146          foreach ($sections as $section) {
 147              $affectedsections[$section->section] = true;
 148              move_section_to($course, $section->section, $targetsection->section);
 149          }
 150  
 151          // Use section_state to return the section and activities updated state.
 152          $this->section_state($updates, $course, $ids, $targetsectionid);
 153  
 154          // All course sections can be renamed because of the resort.
 155          $allsections = $modinfo->get_section_info_all();
 156          foreach ($allsections as $section) {
 157              // Ignore the affected sections because they are already in the updates.
 158              if (isset($affectedsections[$section->section])) {
 159                  continue;
 160              }
 161              $updates->add_section_put($section->id);
 162          }
 163          // The section order is at a course level.
 164          $updates->add_course_put();
 165      }
 166  
 167      /**
 168       * Create a course section.
 169       *
 170       * This method follows the same logic as changenumsections.php.
 171       *
 172       * @param stateupdates $updates the affected course elements track
 173       * @param stdClass $course the course object
 174       * @param int[] $ids not used
 175       * @param int $targetsectionid optional target section id (if not passed section will be appended)
 176       * @param int $targetcmid not used
 177       */
 178      public function section_add(
 179          stateupdates $updates,
 180          stdClass $course,
 181          array $ids = [],
 182          ?int $targetsectionid = null,
 183          ?int $targetcmid = null
 184      ): void {
 185  
 186          $coursecontext = context_course::instance($course->id);
 187          require_capability('moodle/course:update', $coursecontext);
 188  
 189          // Get course format settings.
 190          $format = course_get_format($course->id);
 191          $lastsectionnumber = $format->get_last_section_number();
 192          $maxsections = $format->get_max_sections();
 193  
 194          if ($lastsectionnumber >= $maxsections) {
 195              throw new moodle_exception('maxsectionslimit', 'moodle', $maxsections);
 196          }
 197  
 198          $modinfo = get_fast_modinfo($course);
 199  
 200          // Get target section.
 201          if ($targetsectionid) {
 202              $this->validate_sections($course, [$targetsectionid], __FUNCTION__);
 203              $targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST);
 204              // Inserting sections at any position except in the very end requires capability to move sections.
 205              require_capability('moodle/course:movesections', $coursecontext);
 206              $insertposition = $targetsection->section + 1;
 207          } else {
 208              // Get last section.
 209              $insertposition = 0;
 210          }
 211  
 212          course_create_section($course, $insertposition);
 213  
 214          // Adding a section affects the full course structure.
 215          $this->course_state($updates, $course);
 216      }
 217  
 218      /**
 219       * Delete course sections.
 220       *
 221       * This method follows the same logic as editsection.php.
 222       *
 223       * @param stateupdates $updates the affected course elements track
 224       * @param stdClass $course the course object
 225       * @param int[] $ids section ids
 226       * @param int $targetsectionid not used
 227       * @param int $targetcmid not used
 228       */
 229      public function section_delete(
 230          stateupdates $updates,
 231          stdClass $course,
 232          array $ids = [],
 233          ?int $targetsectionid = null,
 234          ?int $targetcmid = null
 235      ): void {
 236  
 237          $coursecontext = context_course::instance($course->id);
 238          require_capability('moodle/course:update', $coursecontext);
 239          require_capability('moodle/course:movesections', $coursecontext);
 240  
 241          $modinfo = get_fast_modinfo($course);
 242  
 243          foreach ($ids as $sectionid) {
 244              $section = $modinfo->get_section_info_by_id($sectionid, MUST_EXIST);
 245              // Send all activity deletions.
 246              if (!empty($modinfo->sections[$section->section])) {
 247                  foreach ($modinfo->sections[$section->section] as $modnumber) {
 248                      $cm = $modinfo->cms[$modnumber];
 249                      $updates->add_cm_remove($cm->id);
 250                  }
 251              }
 252              course_delete_section($course, $section, true, true);
 253              $updates->add_section_remove($sectionid);
 254          }
 255  
 256          // Removing a section affects the full course structure.
 257          $this->course_state($updates, $course);
 258      }
 259  
 260      /**
 261       * Hide course sections.
 262       *
 263       * @param stateupdates $updates the affected course elements track
 264       * @param stdClass $course the course object
 265       * @param int[] $ids section ids
 266       * @param int $targetsectionid not used
 267       * @param int $targetcmid not used
 268       */
 269      public function section_hide(
 270          stateupdates $updates,
 271          stdClass $course,
 272          array $ids = [],
 273          ?int $targetsectionid = null,
 274          ?int $targetcmid = null
 275      ): void {
 276          $this->set_section_visibility($updates, $course, $ids, 0);
 277      }
 278  
 279      /**
 280       * Show course sections.
 281       *
 282       * @param stateupdates $updates the affected course elements track
 283       * @param stdClass $course the course object
 284       * @param int[] $ids section ids
 285       * @param int $targetsectionid not used
 286       * @param int $targetcmid not used
 287       */
 288      public function section_show(
 289          stateupdates $updates,
 290          stdClass $course,
 291          array $ids = [],
 292          ?int $targetsectionid = null,
 293          ?int $targetcmid = null
 294      ): void {
 295          $this->set_section_visibility($updates, $course, $ids, 1);
 296      }
 297  
 298      /**
 299       * Show course sections.
 300       *
 301       * @param stateupdates $updates the affected course elements track
 302       * @param stdClass $course the course object
 303       * @param int[] $ids section ids
 304       * @param int $visible the new visible value
 305       */
 306      protected function set_section_visibility (
 307          stateupdates $updates,
 308          stdClass $course,
 309          array $ids,
 310          int $visible
 311      ) {
 312          $this->validate_sections($course, $ids, __FUNCTION__);
 313          $coursecontext = context_course::instance($course->id);
 314          require_all_capabilities(['moodle/course:update', 'moodle/course:sectionvisibility'], $coursecontext);
 315  
 316          $modinfo = get_fast_modinfo($course);
 317  
 318          foreach ($ids as $sectionid) {
 319              $section = $modinfo->get_section_info_by_id($sectionid, MUST_EXIST);
 320              course_update_section($course, $section, ['visible' => $visible]);
 321          }
 322          $this->section_state($updates, $course, $ids);
 323      }
 324  
 325      /**
 326       * Show course cms.
 327       *
 328       * @param stateupdates $updates the affected course elements track
 329       * @param stdClass $course the course object
 330       * @param int[] $ids cm ids
 331       * @param int $targetsectionid not used
 332       * @param int $targetcmid not used
 333       */
 334      public function cm_show(
 335          stateupdates $updates,
 336          stdClass $course,
 337          array $ids = [],
 338          ?int $targetsectionid = null,
 339          ?int $targetcmid = null
 340      ): void {
 341          $this->set_cm_visibility($updates, $course, $ids, 1, 1);
 342      }
 343  
 344      /**
 345       * Hide course cms.
 346       *
 347       * @param stateupdates $updates the affected course elements track
 348       * @param stdClass $course the course object
 349       * @param int[] $ids cm ids
 350       * @param int $targetsectionid not used
 351       * @param int $targetcmid not used
 352       */
 353      public function cm_hide(
 354          stateupdates $updates,
 355          stdClass $course,
 356          array $ids = [],
 357          ?int $targetsectionid = null,
 358          ?int $targetcmid = null
 359      ): void {
 360          $this->set_cm_visibility($updates, $course, $ids, 0, 1);
 361      }
 362  
 363      /**
 364       * Stealth course cms.
 365       *
 366       * @param stateupdates $updates the affected course elements track
 367       * @param stdClass $course the course object
 368       * @param int[] $ids cm ids
 369       * @param int $targetsectionid not used
 370       * @param int $targetcmid not used
 371       */
 372      public function cm_stealth(
 373          stateupdates $updates,
 374          stdClass $course,
 375          array $ids = [],
 376          ?int $targetsectionid = null,
 377          ?int $targetcmid = null
 378      ): void {
 379          $this->set_cm_visibility($updates, $course, $ids, 1, 0);
 380      }
 381  
 382      /**
 383       * Internal method to define the cm visibility.
 384       *
 385       * @param stateupdates $updates the affected course elements track
 386       * @param stdClass $course the course object
 387       * @param int[] $ids cm ids
 388       * @param int $visible the new visible value
 389       * @param int $coursevisible the new course visible value
 390       */
 391      protected function set_cm_visibility(
 392          stateupdates $updates,
 393          stdClass $course,
 394          array $ids,
 395          int $visible,
 396          int $coursevisible
 397      ): void {
 398          global $CFG;
 399  
 400          $this->validate_cms($course, $ids, __FUNCTION__);
 401  
 402          // Check capabilities on every activity context.
 403          foreach ($ids as $cmid) {
 404              $modcontext = context_module::instance($cmid);
 405              require_all_capabilities(['moodle/course:manageactivities', 'moodle/course:activityvisibility'], $modcontext);
 406          }
 407  
 408          $format = course_get_format($course->id);
 409          $modinfo = get_fast_modinfo($course);
 410  
 411          $cms = $this->get_cm_info($modinfo, $ids);
 412          foreach ($cms as $cm) {
 413              // Check stealth availability.
 414              if (!$coursevisible) {
 415                  $section = $cm->get_section_info();
 416                  $allowstealth = !empty($CFG->allowstealth) && $format->allow_stealth_module_visibility($cm, $section);
 417                  $coursevisible = ($allowstealth) ? 0 : 1;
 418              }
 419              set_coursemodule_visible($cm->id, $visible, $coursevisible, false);
 420              course_module_updated::create_from_cm($cm, $modcontext)->trigger();
 421          }
 422          course_modinfo::purge_course_modules_cache($course->id, $ids);
 423          rebuild_course_cache($course->id, false, true);
 424  
 425          foreach ($cms as $cm) {
 426              $updates->add_cm_put($cm->id);
 427          }
 428      }
 429  
 430      /**
 431       * Move course cms to the right. Indent = 1.
 432       *
 433       * @param stateupdates $updates the affected course elements track
 434       * @param stdClass $course the course object
 435       * @param int[] $ids cm ids
 436       * @param int $targetsectionid not used
 437       * @param int $targetcmid not used
 438       */
 439      public function cm_moveright(
 440          stateupdates $updates,
 441          stdClass $course,
 442          array $ids = [],
 443          ?int $targetsectionid = null,
 444          ?int $targetcmid = null
 445      ): void {
 446          $this->set_cm_indentation($updates, $course, $ids, 1);
 447      }
 448  
 449      /**
 450       * Move course cms to the left. Indent = 0.
 451       *
 452       * @param stateupdates $updates the affected course elements track
 453       * @param stdClass $course the course object
 454       * @param int[] $ids cm ids
 455       * @param int $targetsectionid not used
 456       * @param int $targetcmid not used
 457       */
 458      public function cm_moveleft(
 459          stateupdates $updates,
 460          stdClass $course,
 461          array $ids = [],
 462          ?int $targetsectionid = null,
 463          ?int $targetcmid = null
 464      ): void {
 465          $this->set_cm_indentation($updates, $course, $ids, 0);
 466      }
 467  
 468      /**
 469       * Internal method to define the cm indentation level.
 470       *
 471       * @param stateupdates $updates the affected course elements track
 472       * @param stdClass $course the course object
 473       * @param int[] $ids cm ids
 474       * @param int $indent new value for indentation
 475       */
 476      protected function set_cm_indentation(
 477          stateupdates $updates,
 478          stdClass $course,
 479          array $ids,
 480          int $indent
 481      ): void {
 482          global $DB;
 483  
 484          $this->validate_cms($course, $ids, __FUNCTION__);
 485  
 486          // Check capabilities on every activity context.
 487          foreach ($ids as $cmid) {
 488              $modcontext = context_module::instance($cmid);
 489              require_capability('moodle/course:manageactivities', $modcontext);
 490          }
 491          $modinfo = get_fast_modinfo($course);
 492          $cms = $this->get_cm_info($modinfo, $ids);
 493          list($insql, $inparams) = $DB->get_in_or_equal(array_keys($cms), SQL_PARAMS_NAMED);
 494          $DB->set_field_select('course_modules', 'indent', $indent, "id $insql", $inparams);
 495          rebuild_course_cache($course->id, false, true);
 496          foreach ($cms as $cm) {
 497              $modcontext = context_module::instance($cm->id);
 498              course_module_updated::create_from_cm($cm, $modcontext)->trigger();
 499              $updates->add_cm_put($cm->id);
 500          }
 501      }
 502  
 503      /**
 504       * Extract several cm_info from the course_modinfo.
 505       *
 506       * @param course_modinfo $modinfo the course modinfo.
 507       * @param int[] $ids the course modules $ids
 508       * @return cm_info[] the extracted cm_info objects
 509       */
 510      protected function get_cm_info (course_modinfo $modinfo, array $ids): array {
 511          $cms = [];
 512          foreach ($ids as $cmid) {
 513              $cms[$cmid] = $modinfo->get_cm($cmid);
 514          }
 515          return $cms;
 516      }
 517  
 518      /**
 519       * Extract several section_info from the course_modinfo.
 520       *
 521       * @param course_modinfo $modinfo the course modinfo.
 522       * @param int[] $ids the course modules $ids
 523       * @return section_info[] the extracted section_info objects
 524       */
 525      protected function get_section_info(course_modinfo $modinfo, array $ids): array {
 526          $sections = [];
 527          foreach ($ids as $sectionid) {
 528              $sections[$sectionid] = $modinfo->get_section_info_by_id($sectionid);
 529          }
 530          return $sections;
 531      }
 532  
 533      /**
 534       * Update the course content section collapsed value.
 535       *
 536       * @param stateupdates $updates the affected course elements track
 537       * @param stdClass $course the course object
 538       * @param int[] $ids the collapsed section ids
 539       * @param int $targetsectionid not used
 540       * @param int $targetcmid not used
 541       */
 542      public function section_content_collapsed(
 543          stateupdates $updates,
 544          stdClass $course,
 545          array $ids = [],
 546          ?int $targetsectionid = null,
 547          ?int $targetcmid = null
 548      ): void {
 549          if (!empty($ids)) {
 550              $this->validate_sections($course, $ids, __FUNCTION__);
 551          }
 552          $format = course_get_format($course->id);
 553          $format->set_sections_preference('contentcollapsed', $ids);
 554      }
 555  
 556      /**
 557       * Update the course index section collapsed value.
 558       *
 559       * @param stateupdates $updates the affected course elements track
 560       * @param stdClass $course the course object
 561       * @param int[] $ids the collapsed section ids
 562       * @param int $targetsectionid not used
 563       * @param int $targetcmid not used
 564       */
 565      public function section_index_collapsed(
 566          stateupdates $updates,
 567          stdClass $course,
 568          array $ids = [],
 569          ?int $targetsectionid = null,
 570          ?int $targetcmid = null
 571      ): void {
 572          if (!empty($ids)) {
 573              $this->validate_sections($course, $ids, __FUNCTION__);
 574          }
 575          $format = course_get_format($course->id);
 576          $format->set_sections_preference('indexcollapsed', $ids);
 577      }
 578  
 579      /**
 580       * Add the update messages of the updated version of any cm and section related to the cm ids.
 581       *
 582       * This action is mainly used by legacy actions to partially update the course state when the
 583       * result of core_course_edit_module is not enough to generate the correct state data.
 584       *
 585       * @param stateupdates $updates the affected course elements track
 586       * @param stdClass $course the course object
 587       * @param int[] $ids the list of affected course module ids
 588       * @param int $targetsectionid optional target section id
 589       * @param int $targetcmid optional target cm id
 590       */
 591      public function cm_state(
 592          stateupdates $updates,
 593          stdClass $course,
 594          array $ids,
 595          ?int $targetsectionid = null,
 596          ?int $targetcmid = null
 597      ): void {
 598  
 599          // Collect all section and cm to return.
 600          $cmids = [];
 601          foreach ($ids as $cmid) {
 602              $cmids[$cmid] = true;
 603          }
 604          if ($targetcmid) {
 605              $cmids[$targetcmid] = true;
 606          }
 607  
 608          $sectionids = [];
 609          if ($targetsectionid) {
 610              $this->validate_sections($course, [$targetsectionid], __FUNCTION__);
 611              $sectionids[$targetsectionid] = true;
 612          }
 613  
 614          $this->validate_cms($course, array_keys($cmids), __FUNCTION__);
 615  
 616          $modinfo = course_modinfo::instance($course);
 617  
 618          foreach (array_keys($cmids) as $cmid) {
 619  
 620              // Add this action to updates array.
 621              $updates->add_cm_put($cmid);
 622  
 623              $cm = $modinfo->get_cm($cmid);
 624              $sectionids[$cm->section] = true;
 625          }
 626  
 627          foreach (array_keys($sectionids) as $sectionid) {
 628              $updates->add_section_put($sectionid);
 629          }
 630      }
 631  
 632      /**
 633       * Add the update messages of the updated version of any cm and section related to the section ids.
 634       *
 635       * This action is mainly used by legacy actions to partially update the course state when the
 636       * result of core_course_edit_module is not enough to generate the correct state data.
 637       *
 638       * @param stateupdates $updates the affected course elements track
 639       * @param stdClass $course the course object
 640       * @param int[] $ids the list of affected course section ids
 641       * @param int $targetsectionid optional target section id
 642       * @param int $targetcmid optional target cm id
 643       */
 644      public function section_state(
 645          stateupdates $updates,
 646          stdClass $course,
 647          array $ids,
 648          ?int $targetsectionid = null,
 649          ?int $targetcmid = null
 650      ): void {
 651  
 652          $cmids = [];
 653          if ($targetcmid) {
 654              $this->validate_cms($course, [$targetcmid], __FUNCTION__);
 655              $cmids[$targetcmid] = true;
 656          }
 657  
 658          $sectionids = [];
 659          foreach ($ids as $sectionid) {
 660              $sectionids[$sectionid] = true;
 661          }
 662          if ($targetsectionid) {
 663              $sectionids[$targetsectionid] = true;
 664          }
 665  
 666          $this->validate_sections($course, array_keys($sectionids), __FUNCTION__);
 667  
 668          $modinfo = course_modinfo::instance($course);
 669  
 670          foreach (array_keys($sectionids) as $sectionid) {
 671              $sectioninfo = $modinfo->get_section_info_by_id($sectionid);
 672              $updates->add_section_put($sectionid);
 673              // Add cms.
 674              if (empty($modinfo->sections[$sectioninfo->section])) {
 675                  continue;
 676              }
 677  
 678              foreach ($modinfo->sections[$sectioninfo->section] as $modnumber) {
 679                  $mod = $modinfo->cms[$modnumber];
 680                  if ($mod->is_visible_on_course_page()) {
 681                      $cmids[$mod->id] = true;
 682                  }
 683              }
 684          }
 685  
 686          foreach (array_keys($cmids) as $cmid) {
 687              // Add this action to updates array.
 688              $updates->add_cm_put($cmid);
 689          }
 690      }
 691  
 692      /**
 693       * Add all the update messages from the complete course state.
 694       *
 695       * This action is mainly used by legacy actions to partially update the course state when the
 696       * result of core_course_edit_module is not enough to generate the correct state data.
 697       *
 698       * @param stateupdates $updates the affected course elements track
 699       * @param stdClass $course the course object
 700       * @param int[] $ids the list of affected course module ids (not used)
 701       * @param int $targetsectionid optional target section id (not used)
 702       * @param int $targetcmid optional target cm id (not used)
 703       */
 704      public function course_state(
 705          stateupdates $updates,
 706          stdClass $course,
 707          array $ids = [],
 708          ?int $targetsectionid = null,
 709          ?int $targetcmid = null
 710      ): void {
 711  
 712          $modinfo = course_modinfo::instance($course);
 713  
 714          $updates->add_course_put();
 715  
 716          // Add sections updates.
 717          $sections = $modinfo->get_section_info_all();
 718          $sectionids = [];
 719          foreach ($sections as $sectioninfo) {
 720              $sectionids[] = $sectioninfo->id;
 721          }
 722          if (!empty($sectionids)) {
 723              $this->section_state($updates, $course, $sectionids);
 724          }
 725      }
 726  
 727      /**
 728       * Checks related to sections: course format support them, all given sections exist and topic 0 is not included.
 729       *
 730       * @param stdClass $course The course where given $sectionids belong.
 731       * @param array $sectionids List of sections to validate.
 732       * @param string|null $info additional information in case of error (default null).
 733       * @throws moodle_exception if any id is not valid
 734       */
 735      protected function validate_sections(stdClass $course, array $sectionids, ?string $info = null): void {
 736          global $DB;
 737  
 738          if (empty($sectionids)) {
 739              throw new moodle_exception('emptysectionids', 'core', null, $info);
 740          }
 741  
 742          // No section actions are allowed if course format does not support sections.
 743          $courseformat = course_get_format($course->id);
 744          if (!$courseformat->uses_sections()) {
 745              throw new moodle_exception('sectionactionnotsupported', 'core', null, $info);
 746          }
 747  
 748          list($insql, $inparams) = $DB->get_in_or_equal($sectionids, SQL_PARAMS_NAMED);
 749  
 750          // Check if all the given sections exist.
 751          $couintsections = $DB->count_records_select('course_sections', "id $insql", $inparams);
 752          if ($couintsections != count($sectionids)) {
 753              throw new moodle_exception('unexistingsectionid', 'core', null, $info);
 754          }
 755      }
 756  
 757      /**
 758       * Checks related to course modules: all given cm exist.
 759       *
 760       * @param stdClass $course The course where given $cmids belong.
 761       * @param array $cmids List of course module ids to validate.
 762       * @param string $info additional information in case of error.
 763       * @throws moodle_exception if any id is not valid
 764       */
 765      protected function validate_cms(stdClass $course, array $cmids, ?string $info = null): void {
 766  
 767          if (empty($cmids)) {
 768              throw new moodle_exception('emptycmids', 'core', null, $info);
 769          }
 770  
 771          $moduleinfo = get_fast_modinfo($course->id);
 772          $intersect = array_intersect($cmids, array_keys($moduleinfo->get_cms()));
 773          if (count($cmids) != count($intersect)) {
 774              throw new moodle_exception('unexistingcmid', 'core', null, $info);
 775          }
 776      }
 777  }