Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
/course/ -> modlib.php (source)

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