Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
/course/ -> modlib.php (source)

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

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