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.
/course/ -> modlib.php (source)

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [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  /**
  18   * Library of functions specific to course/modedit.php and course API functions.
  19   * The course API function calling them are course/lib.php:create_module() and update_module().
  20   * This file has been created has an alternative solution to a full refactor of course/modedit.php
  21   * in order to create the course API functions.
  22   *
  23   * @copyright 2013 Jerome Mouneyrac
  24   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   * @package core_course
  26   */
  27  
  28  defined('MOODLE_INTERNAL') || die;
  29  
  30  use \core_grades\component_gradeitems;
  31  
  32  require_once($CFG->dirroot.'/course/lib.php');
  33  
  34  /**
  35   * Add course module.
  36   *
  37   * The function does not check user capabilities.
  38   * The function creates course module, module instance, add the module to the correct section.
  39   * It also trigger common action that need to be done after adding/updating a module.
  40   *
  41   * @param object $moduleinfo the moudle data
  42   * @param object $course the course of the module
  43   * @param object $mform this is required by an existing hack to deal with files during MODULENAME_add_instance()
  44   * @return object the updated module info
  45   */
  46  function add_moduleinfo($moduleinfo, $course, $mform = null) {
  47      global $DB, $CFG;
  48  
  49      // Attempt to include module library before we make any changes to DB.
  50      include_modulelib($moduleinfo->modulename);
  51  
  52      $moduleinfo->course = $course->id;
  53      $moduleinfo = set_moduleinfo_defaults($moduleinfo);
  54  
  55      if (!empty($course->groupmodeforce) or !isset($moduleinfo->groupmode)) {
  56          $moduleinfo->groupmode = 0; // Do not set groupmode.
  57      }
  58  
  59      // First add course_module record because we need the context.
  60      $newcm = new stdClass();
  61      $newcm->course           = $course->id;
  62      $newcm->module           = $moduleinfo->module;
  63      $newcm->instance         = 0; // Not known yet, will be updated later (this is similar to restore code).
  64      $newcm->visible          = $moduleinfo->visible;
  65      $newcm->visibleold       = $moduleinfo->visible;
  66      $newcm->visibleoncoursepage = $moduleinfo->visibleoncoursepage;
  67      if (isset($moduleinfo->cmidnumber)) {
  68          $newcm->idnumber         = $moduleinfo->cmidnumber;
  69      }
  70      if (isset($moduleinfo->downloadcontent)) {
  71          $newcm->downloadcontent = $moduleinfo->downloadcontent;
  72      }
  73      if (has_capability('moodle/course:setforcedlanguage', context_course::instance($course->id))) {
  74          $newcm->lang = $moduleinfo->lang ?? null;
  75      } else {
  76          $newcm->lang = null;
  77      }
  78      $newcm->groupmode        = $moduleinfo->groupmode;
  79      $newcm->groupingid       = $moduleinfo->groupingid;
  80      $completion = new completion_info($course);
  81      if ($completion->is_enabled()) {
  82          $newcm->completion                = $moduleinfo->completion;
  83          $newcm->completionpassgrade       = $moduleinfo->completionpassgrade ?? 0;
  84          if ($moduleinfo->completiongradeitemnumber === '') {
  85              $newcm->completiongradeitemnumber = null;
  86          } else {
  87              $newcm->completiongradeitemnumber = $moduleinfo->completiongradeitemnumber;
  88          }
  89          $newcm->completionview            = $moduleinfo->completionview;
  90          $newcm->completionexpected        = $moduleinfo->completionexpected;
  91      }
  92      if(!empty($CFG->enableavailability)) {
  93          // This code is used both when submitting the form, which uses a long
  94          // name to avoid clashes, and by unit test code which uses the real
  95          // name in the table.
  96          $newcm->availability = null;
  97          if (property_exists($moduleinfo, 'availabilityconditionsjson')) {
  98              if ($moduleinfo->availabilityconditionsjson !== '') {
  99                  $newcm->availability = $moduleinfo->availabilityconditionsjson;
 100              }
 101          } else if (property_exists($moduleinfo, 'availability')) {
 102              $newcm->availability = $moduleinfo->availability;
 103          }
 104          // If there is any availability data, verify it.
 105          if ($newcm->availability) {
 106              $tree = new \core_availability\tree(json_decode($newcm->availability));
 107              // Save time and database space by setting null if the only data
 108              // is an empty tree.
 109              if ($tree->is_empty()) {
 110                  $newcm->availability = null;
 111              }
 112          }
 113      }
 114      if (isset($moduleinfo->showdescription)) {
 115          $newcm->showdescription = $moduleinfo->showdescription;
 116      } else {
 117          $newcm->showdescription = 0;
 118      }
 119  
 120      // From this point we make database changes, so start transaction.
 121      $transaction = $DB->start_delegated_transaction();
 122  
 123      if (!$moduleinfo->coursemodule = add_course_module($newcm)) {
 124          throw new \moodle_exception('cannotaddcoursemodule');
 125      }
 126  
 127      if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true) &&
 128              isset($moduleinfo->introeditor)) {
 129          $introeditor = $moduleinfo->introeditor;
 130          unset($moduleinfo->introeditor);
 131          $moduleinfo->intro       = $introeditor['text'];
 132          $moduleinfo->introformat = $introeditor['format'];
 133      }
 134  
 135      $addinstancefunction    = $moduleinfo->modulename."_add_instance";
 136      try {
 137          $returnfromfunc = $addinstancefunction($moduleinfo, $mform);
 138      } catch (moodle_exception $e) {
 139          $returnfromfunc = $e;
 140      }
 141      if (!$returnfromfunc or !is_number($returnfromfunc)) {
 142          // Undo everything we can. This is not necessary for databases which
 143          // support transactions, but improves consistency for other databases.
 144          context_helper::delete_instance(CONTEXT_MODULE, $moduleinfo->coursemodule);
 145          $DB->delete_records('course_modules', array('id'=>$moduleinfo->coursemodule));
 146  
 147          if ($returnfromfunc instanceof moodle_exception) {
 148              throw $returnfromfunc;
 149          } else if (!is_number($returnfromfunc)) {
 150              throw new \moodle_exception('invalidfunction', '', course_get_url($course, $moduleinfo->section));
 151          } else {
 152              throw new \moodle_exception('cannotaddnewmodule', '', course_get_url($course, $moduleinfo->section),
 153                  $moduleinfo->modulename);
 154          }
 155      }
 156  
 157      $moduleinfo->instance = $returnfromfunc;
 158  
 159      $DB->set_field('course_modules', 'instance', $returnfromfunc, array('id'=>$moduleinfo->coursemodule));
 160  
 161      // Update embedded links and save files.
 162      $modcontext = context_module::instance($moduleinfo->coursemodule);
 163      if (!empty($introeditor)) {
 164          // This will respect a module that has set a value for intro in it's modname_add_instance() function.
 165          $introeditor['text'] = $moduleinfo->intro;
 166  
 167          $moduleinfo->intro = file_save_draft_area_files($introeditor['itemid'], $modcontext->id,
 168                                                        'mod_'.$moduleinfo->modulename, 'intro', 0,
 169                                                        array('subdirs'=>true), $introeditor['text']);
 170          $DB->set_field($moduleinfo->modulename, 'intro', $moduleinfo->intro, array('id'=>$moduleinfo->instance));
 171      }
 172  
 173      // Add module tags.
 174      if (core_tag_tag::is_enabled('core', 'course_modules') && isset($moduleinfo->tags)) {
 175          core_tag_tag::set_item_tags('core', 'course_modules', $moduleinfo->coursemodule, $modcontext, $moduleinfo->tags);
 176      }
 177  
 178      // Course_modules and course_sections each contain a reference to each other.
 179      // So we have to update one of them twice.
 180      $sectionid = course_add_cm_to_section($course, $moduleinfo->coursemodule, $moduleinfo->section);
 181  
 182      // Trigger event based on the action we did.
 183      // Api create_from_cm expects modname and id property, and we don't want to modify $moduleinfo since we are returning it.
 184      $eventdata = clone $moduleinfo;
 185      $eventdata->modname = $eventdata->modulename;
 186      $eventdata->id = $eventdata->coursemodule;
 187      $event = \core\event\course_module_created::create_from_cm($eventdata, $modcontext);
 188      $event->trigger();
 189  
 190      $moduleinfo = edit_module_post_actions($moduleinfo, $course);
 191      $transaction->allow_commit();
 192  
 193      return $moduleinfo;
 194  }
 195  
 196  /**
 197   * Hook for plugins to take action when a module is created or updated.
 198   *
 199   * @param stdClass $moduleinfo the module info
 200   * @param stdClass $course the course of the module
 201   *
 202   * @return stdClass moduleinfo updated by plugins.
 203   */
 204  function plugin_extend_coursemodule_edit_post_actions($moduleinfo, $course) {
 205      $callbacks = get_plugins_with_function('coursemodule_edit_post_actions', 'lib.php');
 206      foreach ($callbacks as $type => $plugins) {
 207          foreach ($plugins as $plugin => $pluginfunction) {
 208              $moduleinfo = $pluginfunction($moduleinfo, $course);
 209          }
 210      }
 211      return $moduleinfo;
 212  }
 213  
 214  /**
 215   * Common create/update module module actions that need to be processed as soon as a module is created/updaded.
 216   * For example:create grade parent category, add outcomes, rebuild caches, regrade, save plagiarism settings...
 217   * Please note this api does not trigger events as of MOODLE 2.6. Please trigger events before calling this api.
 218   *
 219   * @param object $moduleinfo the module info
 220   * @param object $course the course of the module
 221   *
 222   * @return object moduleinfo update with grading management info
 223   */
 224  function edit_module_post_actions($moduleinfo, $course) {
 225      global $CFG, $USER;
 226      require_once($CFG->libdir.'/gradelib.php');
 227  
 228      $modcontext = context_module::instance($moduleinfo->coursemodule);
 229      $hasgrades = plugin_supports('mod', $moduleinfo->modulename, FEATURE_GRADE_HAS_GRADE, false);
 230      $hasoutcomes = plugin_supports('mod', $moduleinfo->modulename, FEATURE_GRADE_OUTCOMES, true);
 231  
 232      $items = grade_item::fetch_all([
 233          'itemtype' => 'mod',
 234          'itemmodule' => $moduleinfo->modulename,
 235          'iteminstance' => $moduleinfo->instance,
 236          'courseid' => $course->id,
 237      ]);
 238  
 239      // Create parent category if requested and move to correct parent category.
 240      $component = "mod_{$moduleinfo->modulename}";
 241      if ($items) {
 242          foreach ($items as $item) {
 243              $update = false;
 244  
 245              // Sync idnumber with grade_item.
 246              // Note: This only happens for itemnumber 0 at this time.
 247              if ($item->itemnumber == 0 && ($item->idnumber != $moduleinfo->cmidnumber)) {
 248                  $item->idnumber = $moduleinfo->cmidnumber;
 249                  $update = true;
 250              }
 251  
 252              // Determine the grade category.
 253              $gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $item->itemnumber, 'gradecat');
 254              if (property_exists($moduleinfo, $gradecatfieldname)) {
 255                  $gradecat = $moduleinfo->$gradecatfieldname;
 256                  if ($gradecat == -1) {
 257                      $gradecategory = new grade_category();
 258                      $gradecategory->courseid = $course->id;
 259                      $gradecategory->fullname = $moduleinfo->name;
 260                      $gradecategory->insert();
 261  
 262                      $parent = $item->get_parent_category();
 263                      $gradecategory->set_parent($parent->id);
 264                      $gradecat = $gradecategory->id;
 265                  }
 266  
 267                  $oldgradecat = null;
 268                  if ($parent = $item->get_parent_category()) {
 269                      $oldgradecat = $parent->id;
 270                  }
 271                  if ($oldgradecat != $gradecat) {
 272                      $item->set_parent($gradecat);
 273                      $update = true;
 274                  }
 275              }
 276  
 277              // Determine the gradepass.
 278              $gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $item->itemnumber, 'gradepass');
 279              if (isset($moduleinfo->{$gradepassfieldname})) {
 280                  $gradepass = $moduleinfo->{$gradepassfieldname};
 281                  if (null !== $gradepass && $gradepass != $item->gradepass) {
 282                      $item->gradepass = $gradepass;
 283                      $update = true;
 284                  }
 285              }
 286  
 287              if ($update) {
 288                  $item->update();
 289              }
 290  
 291              if (!empty($moduleinfo->add)) {
 292                  $gradecategory = $item->get_parent_category();
 293                  if ($item->set_aggregation_fields_for_aggregation(0, $gradecategory->aggregation)) {
 294                      $item->update();
 295                  }
 296              }
 297          }
 298      }
 299  
 300      require_once($CFG->libdir.'/grade/grade_outcome.php');
 301      // Add outcomes if requested.
 302      if ($hasoutcomes && $outcomes = grade_outcome::fetch_all_available($course->id)) {
 303          // Outcome grade_item.itemnumber start at 1000, there is nothing above outcomes.
 304          $max_itemnumber = 999;
 305          if ($items) {
 306              foreach($items as $item) {
 307                  if ($item->itemnumber > $max_itemnumber) {
 308                      $max_itemnumber = $item->itemnumber;
 309                  }
 310              }
 311          }
 312  
 313          foreach($outcomes as $outcome) {
 314              $elname = 'outcome_'.$outcome->id;
 315  
 316              if (property_exists($moduleinfo, $elname) and $moduleinfo->$elname) {
 317                  // Check if this is a new outcome grade item.
 318                  $outcomeexists = false;
 319                  if ($items) {
 320                      foreach($items as $item) {
 321                          if ($item->outcomeid == $outcome->id) {
 322                              $outcomeexists = true;
 323                              break;
 324                          }
 325                      }
 326                      if ($outcomeexists) {
 327                          continue;
 328                      }
 329                  }
 330  
 331                  $max_itemnumber++;
 332  
 333                  $outcomeitem = new grade_item();
 334                  $outcomeitem->courseid     = $course->id;
 335                  $outcomeitem->itemtype     = 'mod';
 336                  $outcomeitem->itemmodule   = $moduleinfo->modulename;
 337                  $outcomeitem->iteminstance = $moduleinfo->instance;
 338                  $outcomeitem->itemnumber   = $max_itemnumber;
 339                  $outcomeitem->itemname     = $outcome->fullname;
 340                  $outcomeitem->outcomeid    = $outcome->id;
 341                  $outcomeitem->gradetype    = GRADE_TYPE_SCALE;
 342                  $outcomeitem->scaleid      = $outcome->scaleid;
 343                  $outcomeitem->insert();
 344  
 345                  if ($items) {
 346                      // Move the new outcome into the same category and immediately after the first grade item.
 347                      $item = reset($items);
 348                      $outcomeitem->set_parent($item->categoryid);
 349                      $outcomeitem->move_after_sortorder($item->sortorder);
 350                  } else if (isset($moduleinfo->gradecat)) {
 351                      $outcomeitem->set_parent($moduleinfo->gradecat);
 352                  }
 353  
 354                  if (!$outcomeexists) {
 355                      $gradecategory = $outcomeitem->get_parent_category();
 356                      if ($outcomeitem->set_aggregation_fields_for_aggregation(0, $gradecategory->aggregation)) {
 357                          $outcomeitem->update();
 358                      }
 359                  }
 360              }
 361          }
 362      }
 363  
 364      if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_ADVANCED_GRADING, false)
 365              and has_capability('moodle/grade:managegradingforms', $modcontext)) {
 366          require_once($CFG->dirroot.'/grade/grading/lib.php');
 367          $gradingman = get_grading_manager($modcontext, 'mod_'.$moduleinfo->modulename);
 368          $showgradingmanagement = false;
 369          foreach ($gradingman->get_available_areas() as $areaname => $aretitle) {
 370              $formfield = 'advancedgradingmethod_'.$areaname;
 371              if (isset($moduleinfo->{$formfield})) {
 372                  $gradingman->set_area($areaname);
 373                  $methodchanged = $gradingman->set_active_method($moduleinfo->{$formfield});
 374                  if (empty($moduleinfo->{$formfield})) {
 375                      // Going back to the simple direct grading is not a reason to open the management screen.
 376                      $methodchanged = false;
 377                  }
 378                  $showgradingmanagement = $showgradingmanagement || $methodchanged;
 379              }
 380          }
 381          // Update grading management information.
 382          $moduleinfo->gradingman = $gradingman;
 383          $moduleinfo->showgradingmanagement = $showgradingmanagement;
 384      }
 385  
 386      \course_modinfo::purge_course_module_cache($course->id, $moduleinfo->coursemodule);
 387      rebuild_course_cache($course->id, true, true);
 388      if ($hasgrades) {
 389          grade_regrade_final_grades($course->id);
 390      }
 391  
 392      // To be removed (deprecated) with MDL-67526 (both lines).
 393      require_once($CFG->libdir.'/plagiarismlib.php');
 394      plagiarism_save_form_elements($moduleinfo);
 395  
 396      // Allow plugins to extend the course module form.
 397      $moduleinfo = plugin_extend_coursemodule_edit_post_actions($moduleinfo, $course);
 398  
 399      if (!empty($moduleinfo->coursecontentnotification)) {
 400          // Schedule adhoc-task for delivering the course content updated notification.
 401          if ($course->visible && $moduleinfo->visible) {
 402              $adhocktask = new \core_course\task\content_notification_task();
 403              $adhocktask->set_custom_data(
 404                  ['update' => $moduleinfo->update, 'cmid' => $moduleinfo->coursemodule,
 405                  'courseid' => $course->id, 'userfrom' => $USER->id]);
 406              $adhocktask->set_component('course');
 407              \core\task\manager::queue_adhoc_task($adhocktask, true);
 408          }
 409      }
 410  
 411      return $moduleinfo;
 412  }
 413  
 414  /**
 415   * Set module info default values for the unset module attributs.
 416   *
 417   * @param object $moduleinfo the current known data of the module
 418   * @return object the completed module info
 419   */
 420  function set_moduleinfo_defaults($moduleinfo) {
 421  
 422      if (empty($moduleinfo->coursemodule)) {
 423          // Add.
 424          $cm = null;
 425          $moduleinfo->instance     = '';
 426          $moduleinfo->coursemodule = '';
 427      } else {
 428          // Update.
 429          $cm = get_coursemodule_from_id('', $moduleinfo->coursemodule, 0, false, MUST_EXIST);
 430          $moduleinfo->instance     = $cm->instance;
 431          $moduleinfo->coursemodule = $cm->id;
 432      }
 433      // For safety.
 434      $moduleinfo->modulename = clean_param($moduleinfo->modulename, PARAM_PLUGIN);
 435  
 436      if (!isset($moduleinfo->groupingid)) {
 437          $moduleinfo->groupingid = 0;
 438      }
 439  
 440      if (!isset($moduleinfo->name)) { // Label.
 441          $moduleinfo->name = $moduleinfo->modulename;
 442      }
 443  
 444      if (!isset($moduleinfo->completion)) {
 445          $moduleinfo->completion = COMPLETION_DISABLED;
 446      }
 447      if (!isset($moduleinfo->completionview)) {
 448          $moduleinfo->completionview = COMPLETION_VIEW_NOT_REQUIRED;
 449      }
 450      if (!isset($moduleinfo->completionexpected)) {
 451          $moduleinfo->completionexpected = 0;
 452      }
 453  
 454      // Convert the 'use grade' checkbox into a grade-item number: 0 if checked, null if not.
 455      if (isset($moduleinfo->completionusegrade) && $moduleinfo->completionusegrade) {
 456          $moduleinfo->completiongradeitemnumber = 0;
 457      } else if (!isset($moduleinfo->completiongradeitemnumber)) {
 458          // If there is no gradeitemnumber set, make sure to disable completionpassgrade.
 459          $moduleinfo->completionpassgrade = 0;
 460          $moduleinfo->completiongradeitemnumber = null;
 461      }
 462  
 463      if (!isset($moduleinfo->conditiongradegroup)) {
 464          $moduleinfo->conditiongradegroup = array();
 465      }
 466      if (!isset($moduleinfo->conditionfieldgroup)) {
 467          $moduleinfo->conditionfieldgroup = array();
 468      }
 469      if (!isset($moduleinfo->visibleoncoursepage)) {
 470          $moduleinfo->visibleoncoursepage = 1;
 471      }
 472  
 473      if (!isset($moduleinfo->downloadcontent)) {
 474          $moduleinfo->downloadcontent = DOWNLOAD_COURSE_CONTENT_ENABLED;
 475      }
 476  
 477      return $moduleinfo;
 478  }
 479  
 480  /**
 481   * Check that the user can add a module. Also returns some information like the module, context and course section info.
 482   * The fucntion create the course section if it doesn't exist.
 483   *
 484   * @param object $course the course of the module
 485   * @param object $modulename the module name
 486   * @param object $section the section of the module
 487   * @return array list containing module, context, course section.
 488   * @throws moodle_exception if user is not allowed to perform the action or module is not allowed in this course
 489   */
 490  function can_add_moduleinfo($course, $modulename, $section) {
 491      global $DB;
 492  
 493      $module = $DB->get_record('modules', array('name'=>$modulename), '*', MUST_EXIST);
 494  
 495      $context = context_course::instance($course->id);
 496      require_capability('moodle/course:manageactivities', $context);
 497  
 498      course_create_sections_if_missing($course, $section);
 499      $cw = get_fast_modinfo($course)->get_section_info($section);
 500  
 501      if (!course_allowed_module($course, $module->name)) {
 502          throw new \moodle_exception('moduledisable');
 503      }
 504  
 505      return array($module, $context, $cw);
 506  }
 507  
 508  /**
 509   * Check if user is allowed to update module info and returns related item/data to the module.
 510   *
 511   * @param object $cm course module
 512   * @return array - list of course module, context, module, moduleinfo, and course section.
 513   * @throws moodle_exception if user is not allowed to perform the action
 514   */
 515  function can_update_moduleinfo($cm) {
 516      global $DB;
 517  
 518      // Check the $USER has the right capability.
 519      $context = context_module::instance($cm->id);
 520      require_capability('moodle/course:manageactivities', $context);
 521  
 522      // Check module exists.
 523      $module = $DB->get_record('modules', array('id'=>$cm->module), '*', MUST_EXIST);
 524  
 525      // Check the moduleinfo exists.
 526      $data = $DB->get_record($module->name, array('id'=>$cm->instance), '*', MUST_EXIST);
 527  
 528      // Check the course section exists.
 529      $cw = $DB->get_record('course_sections', array('id'=>$cm->section), '*', MUST_EXIST);
 530  
 531      return array($cm, $context, $module, $data, $cw);
 532  }
 533  
 534  
 535  /**
 536   * Update the module info.
 537   * This function doesn't check the user capabilities. It updates the course module and the module instance.
 538   * Then execute common action to create/update module process (trigger event, rebuild cache, save plagiarism settings...).
 539   *
 540   * @param object $cm course module
 541   * @param object $moduleinfo module info
 542   * @param object $course course of the module
 543   * @param object $mform - the mform is required by some specific module in the function MODULE_update_instance(). This is due to a hack in this function.
 544   * @return array list of course module and module info.
 545   */
 546  function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
 547      global $DB, $CFG;
 548  
 549      $data = new stdClass();
 550      if ($mform) {
 551          $data = $mform->get_data();
 552      }
 553  
 554      // Attempt to include module library before we make any changes to DB.
 555      include_modulelib($moduleinfo->modulename);
 556  
 557      $moduleinfo->course = $course->id;
 558      $moduleinfo = set_moduleinfo_defaults($moduleinfo);
 559  
 560      $modcontext = context_module::instance($moduleinfo->coursemodule);
 561      if (has_capability('moodle/course:setforcedlanguage', $modcontext)) {
 562          $cm->lang = $moduleinfo->lang ?? null;
 563      } else {
 564          unset($cm->lang);
 565      }
 566  
 567      if (!empty($course->groupmodeforce) or !isset($moduleinfo->groupmode)) {
 568          $moduleinfo->groupmode = $cm->groupmode; // Keep original.
 569      }
 570  
 571      // Update course module first.
 572      $cm->groupmode = $moduleinfo->groupmode;
 573      if (isset($moduleinfo->groupingid)) {
 574          $cm->groupingid = $moduleinfo->groupingid;
 575      }
 576  
 577      $completion = new completion_info($course);
 578      if ($completion->is_enabled()) {
 579          // Completion settings that would affect users who have already completed
 580          // the activity may be locked; if so, these should not be updated.
 581          if (!empty($moduleinfo->completionunlocked)) {
 582              $cm->completion = $moduleinfo->completion;
 583              $cm->completionpassgrade = $moduleinfo->completionpassgrade ?? 0;
 584              if ($moduleinfo->completiongradeitemnumber === '') {
 585                  $cm->completiongradeitemnumber = null;
 586              } else {
 587                  $cm->completiongradeitemnumber = $moduleinfo->completiongradeitemnumber;
 588              }
 589              $cm->completionview = $moduleinfo->completionview;
 590          }
 591          // The expected date does not affect users who have completed the activity,
 592          // so it is safe to update it regardless of the lock status.
 593          $cm->completionexpected = $moduleinfo->completionexpected;
 594      }
 595      if (!empty($CFG->enableavailability)) {
 596          // This code is used both when submitting the form, which uses a long
 597          // name to avoid clashes, and by unit test code which uses the real
 598          // name in the table.
 599          if (property_exists($moduleinfo, 'availabilityconditionsjson')) {
 600              if ($moduleinfo->availabilityconditionsjson !== '') {
 601                  $cm->availability = $moduleinfo->availabilityconditionsjson;
 602              } else {
 603                  $cm->availability = null;
 604              }
 605          } else if (property_exists($moduleinfo, 'availability')) {
 606              $cm->availability = $moduleinfo->availability;
 607          }
 608          // If there is any availability data, verify it.
 609          if ($cm->availability) {
 610              $tree = new \core_availability\tree(json_decode($cm->availability));
 611              // Save time and database space by setting null if the only data
 612              // is an empty tree.
 613              if ($tree->is_empty()) {
 614                  $cm->availability = null;
 615              }
 616          }
 617      }
 618      if (isset($moduleinfo->showdescription)) {
 619          $cm->showdescription = $moduleinfo->showdescription;
 620      } else {
 621          $cm->showdescription = 0;
 622      }
 623  
 624      $DB->update_record('course_modules', $cm);
 625  
 626      // Update embedded links and save files.
 627      if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) {
 628          $moduleinfo->intro = file_save_draft_area_files($moduleinfo->introeditor['itemid'], $modcontext->id,
 629                                                        'mod_'.$moduleinfo->modulename, 'intro', 0,
 630                                                        array('subdirs'=>true), $moduleinfo->introeditor['text']);
 631          $moduleinfo->introformat = $moduleinfo->introeditor['format'];
 632          unset($moduleinfo->introeditor);
 633      }
 634      // Get the a copy of the grade_item before it is modified incase we need to scale the grades.
 635      $oldgradeitem = null;
 636      $newgradeitem = null;
 637      if (!empty($data->grade_rescalegrades) && $data->grade_rescalegrades == 'yes') {
 638          // Fetch the grade item before it is updated.
 639          $oldgradeitem = grade_item::fetch(array('itemtype' => 'mod',
 640                                                  'itemmodule' => $moduleinfo->modulename,
 641                                                  'iteminstance' => $moduleinfo->instance,
 642                                                  'itemnumber' => 0,
 643                                                  'courseid' => $moduleinfo->course));
 644      }
 645  
 646      $updateinstancefunction = $moduleinfo->modulename."_update_instance";
 647      if (!$updateinstancefunction($moduleinfo, $mform)) {
 648          throw new \moodle_exception('cannotupdatemod', '', course_get_url($course, $cm->section), $moduleinfo->modulename);
 649      }
 650  
 651      // This needs to happen AFTER the grademin/grademax have already been updated.
 652      if (!empty($data->grade_rescalegrades) && $data->grade_rescalegrades == 'yes') {
 653          // Get the grade_item after the update call the activity to scale the grades.
 654          $newgradeitem = grade_item::fetch(array('itemtype' => 'mod',
 655                                                  'itemmodule' => $moduleinfo->modulename,
 656                                                  'iteminstance' => $moduleinfo->instance,
 657                                                  'itemnumber' => 0,
 658                                                  'courseid' => $moduleinfo->course));
 659          if ($newgradeitem && $oldgradeitem->gradetype == GRADE_TYPE_VALUE && $newgradeitem->gradetype == GRADE_TYPE_VALUE) {
 660              $params = array(
 661                  $course,
 662                  $cm,
 663                  $oldgradeitem->grademin,
 664                  $oldgradeitem->grademax,
 665                  $newgradeitem->grademin,
 666                  $newgradeitem->grademax
 667              );
 668              if (!component_callback('mod_' . $moduleinfo->modulename, 'rescale_activity_grades', $params)) {
 669                  throw new \moodle_exception('cannotreprocessgrades', '', course_get_url($course, $cm->section),
 670                      $moduleinfo->modulename);
 671              }
 672          }
 673      }
 674  
 675      // Make sure visibility is set correctly (in particular in calendar).
 676      if (has_capability('moodle/course:activityvisibility', $modcontext)) {
 677          set_coursemodule_visible($moduleinfo->coursemodule, $moduleinfo->visible, $moduleinfo->visibleoncoursepage);
 678      }
 679  
 680      if (isset($moduleinfo->cmidnumber)) { // Label.
 681          // Set cm idnumber - uniqueness is already verified by form validation.
 682          set_coursemodule_idnumber($moduleinfo->coursemodule, $moduleinfo->cmidnumber);
 683      }
 684  
 685      if (isset($moduleinfo->downloadcontent)) {
 686          set_downloadcontent($moduleinfo->coursemodule, $moduleinfo->downloadcontent);
 687      }
 688  
 689      // Update module tags.
 690      if (core_tag_tag::is_enabled('core', 'course_modules') && isset($moduleinfo->tags)) {
 691          core_tag_tag::set_item_tags('core', 'course_modules', $moduleinfo->coursemodule, $modcontext, $moduleinfo->tags);
 692      }
 693      $moduleinfo = edit_module_post_actions($moduleinfo, $course);
 694  
 695      // Now that module is fully updated, also update completion data if required.
 696      // (this will wipe all user completion data and recalculate it)
 697      if ($completion->is_enabled() && !empty($moduleinfo->completionunlocked)) {
 698          // Rebuild course cache before resetting completion states to ensure that the cm_info attributes are up to date.
 699          course_modinfo::build_course_cache($course);
 700          // Fetch this course module's info.
 701          $cminfo = cm_info::create($cm);
 702          $completion->reset_all_state($cminfo);
 703      }
 704      $cm->name = $moduleinfo->name;
 705      \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
 706  
 707      return array($cm, $moduleinfo);
 708  }
 709  
 710  /**
 711   * Include once the module lib file.
 712   *
 713   * @param string $modulename module name of the lib to include
 714   * @throws moodle_exception if lib.php file for the module does not exist
 715   */
 716  function include_modulelib($modulename) {
 717      global $CFG;
 718      $modlib = "$CFG->dirroot/mod/$modulename/lib.php";
 719      if (file_exists($modlib)) {
 720          include_once($modlib);
 721      } else {
 722          throw new moodle_exception('modulemissingcode', '', '', $modlib);
 723      }
 724  }
 725  
 726  /**
 727   * Get module information data required for updating the module.
 728   *
 729   * @param  stdClass $cm     course module object
 730   * @param  stdClass $course course object
 731   * @return array required data for updating a module
 732   * @since  Moodle 3.2
 733   */
 734  function get_moduleinfo_data($cm, $course) {
 735      global $CFG;
 736  
 737      list($cm, $context, $module, $data, $cw) = can_update_moduleinfo($cm);
 738  
 739      $data->coursemodule       = $cm->id;
 740      $data->section            = $cw->section;  // The section number itself - relative!!! (section column in course_sections)
 741      $data->visible            = $cm->visible; //??  $cw->visible ? $cm->visible : 0; // section hiding overrides
 742      $data->visibleoncoursepage = $cm->visibleoncoursepage;
 743      $data->cmidnumber         = $cm->idnumber;          // The cm IDnumber
 744      $data->groupmode          = groups_get_activity_groupmode($cm); // locked later if forced
 745      $data->groupingid         = $cm->groupingid;
 746      $data->course             = $course->id;
 747      $data->module             = $module->id;
 748      $data->modulename         = $module->name;
 749      $data->instance           = $cm->instance;
 750      $data->completion         = $cm->completion;
 751      $data->completionview     = $cm->completionview;
 752      $data->completionexpected = $cm->completionexpected;
 753      $data->completionusegrade = is_null($cm->completiongradeitemnumber) ? 0 : 1;
 754      $data->completionpassgrade = $cm->completionpassgrade;
 755      $data->completiongradeitemnumber = $cm->completiongradeitemnumber;
 756      $data->showdescription    = $cm->showdescription;
 757      $data->downloadcontent    = $cm->downloadcontent;
 758      $data->lang               = $cm->lang;
 759      $data->tags               = core_tag_tag::get_item_tags_array('core', 'course_modules', $cm->id);
 760      if (!empty($CFG->enableavailability)) {
 761          $data->availabilityconditionsjson = $cm->availability;
 762      }
 763  
 764      if (plugin_supports('mod', $data->modulename, FEATURE_MOD_INTRO, true)) {
 765          $draftid_editor = file_get_submitted_draft_itemid('introeditor');
 766          $currentintro = file_prepare_draft_area($draftid_editor, $context->id, 'mod_'.$data->modulename, 'intro', 0, array('subdirs'=>true), $data->intro);
 767          $data->introeditor = array('text'=>$currentintro, 'format'=>$data->introformat, 'itemid'=>$draftid_editor);
 768      }
 769  
 770      if (plugin_supports('mod', $data->modulename, FEATURE_ADVANCED_GRADING, false)
 771              and has_capability('moodle/grade:managegradingforms', $context)) {
 772          require_once($CFG->dirroot.'/grade/grading/lib.php');
 773          $gradingman = get_grading_manager($context, 'mod_'.$data->modulename);
 774          $data->_advancedgradingdata['methods'] = $gradingman->get_available_methods();
 775          $areas = $gradingman->get_available_areas();
 776  
 777          foreach ($areas as $areaname => $areatitle) {
 778              $gradingman->set_area($areaname);
 779              $method = $gradingman->get_active_method();
 780              $data->_advancedgradingdata['areas'][$areaname] = array(
 781                  'title'  => $areatitle,
 782                  'method' => $method,
 783              );
 784              $formfield = 'advancedgradingmethod_'.$areaname;
 785              $data->{$formfield} = $method;
 786          }
 787      }
 788  
 789      $component = "mod_{$data->modulename}";
 790      $items = grade_item::fetch_all([
 791          'itemtype' => 'mod',
 792          'itemmodule' => $data->modulename,
 793          'iteminstance' => $data->instance,
 794          'courseid' => $course->id,
 795      ]);
 796  
 797      if ($items) {
 798          // Add existing outcomes.
 799          foreach ($items as $item) {
 800              if (!empty($item->outcomeid)) {
 801                  $data->{'outcome_' . $item->outcomeid} = 1;
 802              } else if (isset($item->gradepass)) {
 803                  $gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $item->itemnumber, 'gradepass');
 804                  $data->{$gradepassfieldname} = format_float($item->gradepass, $item->get_decimals());
 805              }
 806  
 807          }
 808  
 809          // set category if present
 810          $gradecat = [];
 811          foreach ($items as $item) {
 812              if (!isset($gradecat[$item->itemnumber])) {
 813                  $gradecat[$item->itemnumber] = $item->categoryid;
 814              }
 815              if ($gradecat[$item->itemnumber] != $item->categoryid) {
 816                  // Mixed categories.
 817                  $gradecat[$item->itemnumber] = false;
 818              }
 819          }
 820          foreach ($gradecat as $itemnumber => $cat) {
 821              if ($cat !== false) {
 822                  $gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradecat');
 823                  // Do not set if mixed categories present.
 824                  $data->{$gradecatfieldname} = $cat;
 825              }
 826          }
 827      }
 828      return array($cm, $context, $module, $data, $cw);
 829  }
 830  
 831  /**
 832   * Prepare the standard module information for a new module instance.
 833   *
 834   * @param  stdClass $course  course object
 835   * @param  string $modulename  module name
 836   * @param  int $section section number
 837   * @return array module information about other required data
 838   * @since  Moodle 3.2
 839   */
 840  function prepare_new_moduleinfo_data($course, $modulename, $section) {
 841      global $CFG;
 842  
 843      list($module, $context, $cw) = can_add_moduleinfo($course, $modulename, $section);
 844  
 845      $cm = null;
 846  
 847      $data = new stdClass();
 848      $data->section          = $section;  // The section number itself - relative!!! (section column in course_sections)
 849      $data->visible          = $cw->visible;
 850      $data->course           = $course->id;
 851      $data->module           = $module->id;
 852      $data->modulename       = $module->name;
 853      $data->groupmode        = $course->groupmode;
 854      $data->groupingid       = $course->defaultgroupingid;
 855      $data->id               = '';
 856      $data->instance         = '';
 857      $data->coursemodule     = '';
 858      $data->downloadcontent  = DOWNLOAD_COURSE_CONTENT_ENABLED;
 859  
 860      // Apply completion defaults.
 861      $defaults = \core_completion\manager::get_default_completion($course, $module);
 862      foreach ($defaults as $key => $value) {
 863          $data->$key = $value;
 864      }
 865  
 866      if (plugin_supports('mod', $data->modulename, FEATURE_MOD_INTRO, true)) {
 867          $draftid_editor = file_get_submitted_draft_itemid('introeditor');
 868          file_prepare_draft_area($draftid_editor, null, null, null, null, array('subdirs'=>true));
 869          $data->introeditor = array('text' => '', 'format' => editors_get_preferred_format(), 'itemid' => $draftid_editor);
 870      }
 871  
 872      if (plugin_supports('mod', $data->modulename, FEATURE_ADVANCED_GRADING, false)
 873              and has_capability('moodle/grade:managegradingforms', $context)) {
 874          require_once($CFG->dirroot.'/grade/grading/lib.php');
 875  
 876          $data->_advancedgradingdata['methods'] = grading_manager::available_methods();
 877          $areas = grading_manager::available_areas('mod_'.$module->name);
 878  
 879          foreach ($areas as $areaname => $areatitle) {
 880              $data->_advancedgradingdata['areas'][$areaname] = array(
 881                  'title'  => $areatitle,
 882                  'method' => '',
 883              );
 884              $formfield = 'advancedgradingmethod_'.$areaname;
 885              $data->{$formfield} = '';
 886          }
 887      }
 888  
 889      return array($module, $context, $cw, $cm, $data);
 890  }