Search moodle.org's
Developer Documentation

See Release Notes

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

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  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) && $moduleinfo->completionusegrade) {
 468          $moduleinfo->completiongradeitemnumber = 0;
 469      } else if (!isset($moduleinfo->completiongradeitemnumber)) {
 470          // If there is no gradeitemnumber set, make sure to disable completionpassgrade.
 471          $moduleinfo->completionpassgrade = 0;
 472          $moduleinfo->completiongradeitemnumber = null;
 473      }
 474  
 475      if (!isset($moduleinfo->conditiongradegroup)) {
 476          $moduleinfo->conditiongradegroup = array();
 477      }
 478      if (!isset($moduleinfo->conditionfieldgroup)) {
 479          $moduleinfo->conditionfieldgroup = array();
 480      }
 481      if (!isset($moduleinfo->visibleoncoursepage)) {
 482          $moduleinfo->visibleoncoursepage = 1;
 483      }
 484  
 485      if (!isset($moduleinfo->downloadcontent)) {
 486          $moduleinfo->downloadcontent = DOWNLOAD_COURSE_CONTENT_ENABLED;
 487      }
 488  
 489      return $moduleinfo;
 490  }
 491  
 492  /**
 493   * Check that the user can add a module. Also returns some information like the module, context and course section info.
 494   * The fucntion create the course section if it doesn't exist.
 495   *
 496   * @param object $course the course of the module
 497   * @param object $modulename the module name
 498   * @param object $section the section of the module
 499   * @return array list containing module, context, course section.
 500   * @throws moodle_exception if user is not allowed to perform the action or module is not allowed in this course
 501   */
 502  function can_add_moduleinfo($course, $modulename, $section) {
 503      global $DB;
 504  
 505      $module = $DB->get_record('modules', array('name'=>$modulename), '*', MUST_EXIST);
 506  
 507      $context = context_course::instance($course->id);
 508      require_capability('moodle/course:manageactivities', $context);
 509  
 510      course_create_sections_if_missing($course, $section);
 511      $cw = get_fast_modinfo($course)->get_section_info($section);
 512  
 513      if (!course_allowed_module($course, $module->name)) {
 514          throw new \moodle_exception('moduledisable');
 515      }
 516  
 517      return array($module, $context, $cw);
 518  }
 519  
 520  /**
 521   * Check if user is allowed to update module info and returns related item/data to the module.
 522   *
 523   * @param object $cm course module
 524   * @return array - list of course module, context, module, moduleinfo, and course section.
 525   * @throws moodle_exception if user is not allowed to perform the action
 526   */
 527  function can_update_moduleinfo($cm) {
 528      global $DB;
 529  
 530      // Check the $USER has the right capability.
 531      $context = context_module::instance($cm->id);
 532      require_capability('moodle/course:manageactivities', $context);
 533  
 534      // Check module exists.
 535      $module = $DB->get_record('modules', array('id'=>$cm->module), '*', MUST_EXIST);
 536  
 537      // Check the moduleinfo exists.
 538      $data = $DB->get_record($module->name, array('id'=>$cm->instance), '*', MUST_EXIST);
 539  
 540      // Check the course section exists.
 541      $cw = $DB->get_record('course_sections', array('id'=>$cm->section), '*', MUST_EXIST);
 542  
 543      return array($cm, $context, $module, $data, $cw);
 544  }
 545  
 546  
 547  /**
 548   * Update the module info.
 549   * This function doesn't check the user capabilities. It updates the course module and the module instance.
 550   * Then execute common action to create/update module process (trigger event, rebuild cache, save plagiarism settings...).
 551   *
 552   * @param object $cm course module
 553   * @param object $moduleinfo module info
 554   * @param object $course course of the module
 555   * @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.
 556   * @return array list of course module and module info.
 557   */
 558  function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
 559      global $DB, $CFG;
 560  
 561      $data = new stdClass();
 562      if ($mform) {
 563          $data = $mform->get_data();
 564      }
 565  
 566      // Attempt to include module library before we make any changes to DB.
 567      include_modulelib($moduleinfo->modulename);
 568  
 569      $moduleinfo->course = $course->id;
 570      $moduleinfo = set_moduleinfo_defaults($moduleinfo);
 571  
 572      $modcontext = context_module::instance($moduleinfo->coursemodule);
 573      if (has_capability('moodle/course:setforcedlanguage', $modcontext)) {
 574          $cm->lang = $moduleinfo->lang ?? null;
 575      } else {
 576          unset($cm->lang);
 577      }
 578  
 579      if (!empty($course->groupmodeforce) or !isset($moduleinfo->groupmode)) {
 580          $moduleinfo->groupmode = $cm->groupmode; // Keep original.
 581      }
 582  
 583      // Update course module first.
 584      $cm->groupmode = $moduleinfo->groupmode;
 585      if (isset($moduleinfo->groupingid)) {
 586          $cm->groupingid = $moduleinfo->groupingid;
 587      }
 588  
 589      $completion = new completion_info($course);
 590      if ($completion->is_enabled()) {
 591          // Completion settings that would affect users who have already completed
 592          // the activity may be locked; if so, these should not be updated.
 593          if (!empty($moduleinfo->completionunlocked)) {
 594              $cm->completion = $moduleinfo->completion;
 595              $cm->completionpassgrade = $moduleinfo->completionpassgrade ?? 0;
 596              if ($moduleinfo->completiongradeitemnumber === '') {
 597                  $cm->completiongradeitemnumber = null;
 598              } else {
 599                  $cm->completiongradeitemnumber = $moduleinfo->completiongradeitemnumber;
 600              }
 601              $cm->completionview = $moduleinfo->completionview;
 602          }
 603          // The expected date does not affect users who have completed the activity,
 604          // so it is safe to update it regardless of the lock status.
 605          $cm->completionexpected = $moduleinfo->completionexpected;
 606      }
 607      if (!empty($CFG->enableavailability)) {
 608          // This code is used both when submitting the form, which uses a long
 609          // name to avoid clashes, and by unit test code which uses the real
 610          // name in the table.
 611          if (property_exists($moduleinfo, 'availabilityconditionsjson')) {
 612              if ($moduleinfo->availabilityconditionsjson !== '') {
 613                  $cm->availability = $moduleinfo->availabilityconditionsjson;
 614              } else {
 615                  $cm->availability = null;
 616              }
 617          } else if (property_exists($moduleinfo, 'availability')) {
 618              $cm->availability = $moduleinfo->availability;
 619          }
 620          // If there is any availability data, verify it.
 621          if ($cm->availability) {
 622              $tree = new \core_availability\tree(json_decode($cm->availability));
 623              // Save time and database space by setting null if the only data
 624              // is an empty tree.
 625              if ($tree->is_empty()) {
 626                  $cm->availability = null;
 627              }
 628          }
 629      }
 630      if (isset($moduleinfo->showdescription)) {
 631          $cm->showdescription = $moduleinfo->showdescription;
 632      } else {
 633          $cm->showdescription = 0;
 634      }
 635  
 636      $DB->update_record('course_modules', $cm);
 637  
 638      // Update embedded links and save files.
 639      if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) {
 640          $moduleinfo->intro = file_save_draft_area_files($moduleinfo->introeditor['itemid'], $modcontext->id,
 641                                                        'mod_'.$moduleinfo->modulename, 'intro', 0,
 642                                                        array('subdirs'=>true), $moduleinfo->introeditor['text']);
 643          $moduleinfo->introformat = $moduleinfo->introeditor['format'];
 644          unset($moduleinfo->introeditor);
 645      }
 646      // Get the a copy of the grade_item before it is modified incase we need to scale the grades.
 647      $oldgradeitem = null;
 648      $newgradeitem = null;
 649      if (!empty($data->grade_rescalegrades) && $data->grade_rescalegrades == 'yes') {
 650          // Fetch the grade item before it is updated.
 651          $oldgradeitem = grade_item::fetch(array('itemtype' => 'mod',
 652                                                  'itemmodule' => $moduleinfo->modulename,
 653                                                  'iteminstance' => $moduleinfo->instance,
 654                                                  'itemnumber' => 0,
 655                                                  'courseid' => $moduleinfo->course));
 656      }
 657  
 658      $updateinstancefunction = $moduleinfo->modulename."_update_instance";
 659      if (!$updateinstancefunction($moduleinfo, $mform)) {
 660          throw new \moodle_exception('cannotupdatemod', '', course_get_url($course, $cm->section), $moduleinfo->modulename);
 661      }
 662  
 663      // This needs to happen AFTER the grademin/grademax have already been updated.
 664      if (!empty($data->grade_rescalegrades) && $data->grade_rescalegrades == 'yes') {
 665          // Get the grade_item after the update call the activity to scale the grades.
 666          $newgradeitem = grade_item::fetch(array('itemtype' => 'mod',
 667                                                  'itemmodule' => $moduleinfo->modulename,
 668                                                  'iteminstance' => $moduleinfo->instance,
 669                                                  'itemnumber' => 0,
 670                                                  'courseid' => $moduleinfo->course));
 671          if ($newgradeitem && $oldgradeitem->gradetype == GRADE_TYPE_VALUE && $newgradeitem->gradetype == GRADE_TYPE_VALUE) {
 672              $params = array(
 673                  $course,
 674                  $cm,
 675                  $oldgradeitem->grademin,
 676                  $oldgradeitem->grademax,
 677                  $newgradeitem->grademin,
 678                  $newgradeitem->grademax
 679              );
 680              if (!component_callback('mod_' . $moduleinfo->modulename, 'rescale_activity_grades', $params)) {
 681                  throw new \moodle_exception('cannotreprocessgrades', '', course_get_url($course, $cm->section),
 682                      $moduleinfo->modulename);
 683              }
 684          }
 685      }
 686  
 687      // Make sure visibility is set correctly (in particular in calendar).
 688      if (has_capability('moodle/course:activityvisibility', $modcontext)) {
 689          set_coursemodule_visible($moduleinfo->coursemodule, $moduleinfo->visible, $moduleinfo->visibleoncoursepage);
 690      }
 691  
 692      if (isset($moduleinfo->cmidnumber)) { // Label.
 693          // Set cm idnumber - uniqueness is already verified by form validation.
 694          set_coursemodule_idnumber($moduleinfo->coursemodule, $moduleinfo->cmidnumber);
 695      }
 696  
 697      if (isset($moduleinfo->downloadcontent)) {
 698          set_downloadcontent($moduleinfo->coursemodule, $moduleinfo->downloadcontent);
 699      }
 700  
 701      // Update module tags.
 702      if (core_tag_tag::is_enabled('core', 'course_modules') && isset($moduleinfo->tags)) {
 703          core_tag_tag::set_item_tags('core', 'course_modules', $moduleinfo->coursemodule, $modcontext, $moduleinfo->tags);
 704      }
 705      $moduleinfo = edit_module_post_actions($moduleinfo, $course);
 706  
 707      // Now that module is fully updated, also update completion data if required.
 708      // (this will wipe all user completion data and recalculate it)
 709      if ($completion->is_enabled() && !empty($moduleinfo->completionunlocked)) {
 710          // Rebuild course cache before resetting completion states to ensure that the cm_info attributes are up to date.
 711          course_modinfo::build_course_cache($course);
 712          // Fetch this course module's info.
 713          $cminfo = cm_info::create($cm);
 714          $completion->reset_all_state($cminfo);
 715      }
 716      $cm->name = $moduleinfo->name;
 717      \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
 718  
 719      return array($cm, $moduleinfo);
 720  }
 721  
 722  /**
 723   * Include once the module lib file.
 724   *
 725   * @param string $modulename module name of the lib to include
 726   * @throws moodle_exception if lib.php file for the module does not exist
 727   */
 728  function include_modulelib($modulename) {
 729      global $CFG;
 730      $modlib = "$CFG->dirroot/mod/$modulename/lib.php";
 731      if (file_exists($modlib)) {
 732          include_once($modlib);
 733      } else {
 734          throw new moodle_exception('modulemissingcode', '', '', $modlib);
 735      }
 736  }
 737  
 738  /**
 739   * Get module information data required for updating the module.
 740   *
 741   * @param  stdClass $cm     course module object
 742   * @param  stdClass $course course object
 743   * @return array required data for updating a module
 744   * @since  Moodle 3.2
 745   */
 746  function get_moduleinfo_data($cm, $course) {
 747      global $CFG;
 748  
 749      list($cm, $context, $module, $data, $cw) = can_update_moduleinfo($cm);
 750  
 751      $data->coursemodule       = $cm->id;
 752      $data->section            = $cw->section;  // The section number itself - relative!!! (section column in course_sections)
 753      $data->visible            = $cm->visible; //??  $cw->visible ? $cm->visible : 0; // section hiding overrides
 754      $data->visibleoncoursepage = $cm->visibleoncoursepage;
 755      $data->cmidnumber         = $cm->idnumber;          // The cm IDnumber
 756      $data->groupmode          = groups_get_activity_groupmode($cm); // locked later if forced
 757      $data->groupingid         = $cm->groupingid;
 758      $data->course             = $course->id;
 759      $data->module             = $module->id;
 760      $data->modulename         = $module->name;
 761      $data->instance           = $cm->instance;
 762      $data->completion         = $cm->completion;
 763      $data->completionview     = $cm->completionview;
 764      $data->completionexpected = $cm->completionexpected;
 765      $data->completionusegrade = is_null($cm->completiongradeitemnumber) ? 0 : 1;
 766      $data->completionpassgrade = $cm->completionpassgrade;
 767      $data->completiongradeitemnumber = $cm->completiongradeitemnumber;
 768      $data->showdescription    = $cm->showdescription;
 769      $data->downloadcontent    = $cm->downloadcontent;
 770      $data->lang               = $cm->lang;
 771      $data->tags               = core_tag_tag::get_item_tags_array('core', 'course_modules', $cm->id);
 772      if (!empty($CFG->enableavailability)) {
 773          $data->availabilityconditionsjson = $cm->availability;
 774      }
 775  
 776      if (plugin_supports('mod', $data->modulename, FEATURE_MOD_INTRO, true)) {
 777          $draftid_editor = file_get_submitted_draft_itemid('introeditor');
 778          $currentintro = file_prepare_draft_area($draftid_editor, $context->id, 'mod_'.$data->modulename, 'intro', 0, array('subdirs'=>true), $data->intro);
 779          $data->introeditor = array('text'=>$currentintro, 'format'=>$data->introformat, 'itemid'=>$draftid_editor);
 780      }
 781  
 782      if (plugin_supports('mod', $data->modulename, FEATURE_ADVANCED_GRADING, false)
 783              and has_capability('moodle/grade:managegradingforms', $context)) {
 784          require_once($CFG->dirroot.'/grade/grading/lib.php');
 785          $gradingman = get_grading_manager($context, 'mod_'.$data->modulename);
 786          $data->_advancedgradingdata['methods'] = $gradingman->get_available_methods();
 787          $areas = $gradingman->get_available_areas();
 788  
 789          foreach ($areas as $areaname => $areatitle) {
 790              $gradingman->set_area($areaname);
 791              $method = $gradingman->get_active_method();
 792              $data->_advancedgradingdata['areas'][$areaname] = array(
 793                  'title'  => $areatitle,
 794                  'method' => $method,
 795              );
 796              $formfield = 'advancedgradingmethod_'.$areaname;
 797              $data->{$formfield} = $method;
 798          }
 799      }
 800  
 801      $component = "mod_{$data->modulename}";
 802      $items = grade_item::fetch_all([
 803          'itemtype' => 'mod',
 804          'itemmodule' => $data->modulename,
 805          'iteminstance' => $data->instance,
 806          'courseid' => $course->id,
 807      ]);
 808  
 809      if ($items) {
 810          // Add existing outcomes.
 811          foreach ($items as $item) {
 812              if (!empty($item->outcomeid)) {
 813                  $data->{'outcome_' . $item->outcomeid} = 1;
 814              } else if (isset($item->gradepass)) {
 815                  $gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $item->itemnumber, 'gradepass');
 816                  $data->{$gradepassfieldname} = format_float($item->gradepass, $item->get_decimals());
 817              }
 818  
 819          }
 820  
 821          // set category if present
 822          $gradecat = [];
 823          foreach ($items as $item) {
 824              if (!isset($gradecat[$item->itemnumber])) {
 825                  $gradecat[$item->itemnumber] = $item->categoryid;
 826              }
 827              if ($gradecat[$item->itemnumber] != $item->categoryid) {
 828                  // Mixed categories.
 829                  $gradecat[$item->itemnumber] = false;
 830              }
 831          }
 832          foreach ($gradecat as $itemnumber => $cat) {
 833              if ($cat !== false) {
 834                  $gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradecat');
 835                  // Do not set if mixed categories present.
 836                  $data->{$gradecatfieldname} = $cat;
 837              }
 838          }
 839      }
 840      return array($cm, $context, $module, $data, $cw);
 841  }
 842  
 843  /**
 844   * Prepare the standard module information for a new module instance.
 845   *
 846   * @param  stdClass $course  course object
 847   * @param  string $modulename  module name
 848   * @param  int $section section number
 849   * @return array module information about other required data
 850   * @since  Moodle 3.2
 851   */
 852  function prepare_new_moduleinfo_data($course, $modulename, $section) {
 853      global $CFG;
 854  
 855      list($module, $context, $cw) = can_add_moduleinfo($course, $modulename, $section);
 856  
 857      $cm = null;
 858  
 859      $data = new stdClass();
 860      $data->section          = $section;  // The section number itself - relative!!! (section column in course_sections)
 861      $data->visible          = $cw->visible;
 862      $data->course           = $course->id;
 863      $data->module           = $module->id;
 864      $data->modulename       = $module->name;
 865      $data->groupmode        = $course->groupmode;
 866      $data->groupingid       = $course->defaultgroupingid;
 867      $data->id               = '';
 868      $data->instance         = '';
 869      $data->coursemodule     = '';
 870      $data->downloadcontent  = DOWNLOAD_COURSE_CONTENT_ENABLED;
 871  
 872      // Apply completion defaults.
 873      $defaults = \core_completion\manager::get_default_completion($course, $module);
 874      foreach ($defaults as $key => $value) {
 875          $data->$key = $value;
 876      }
 877  
 878      if (plugin_supports('mod', $data->modulename, FEATURE_MOD_INTRO, true)) {
 879          $draftid_editor = file_get_submitted_draft_itemid('introeditor');
 880          file_prepare_draft_area($draftid_editor, null, null, null, null, array('subdirs'=>true));
 881          $data->introeditor = array('text' => '', 'format' => editors_get_preferred_format(), 'itemid' => $draftid_editor);
 882      }
 883  
 884      if (plugin_supports('mod', $data->modulename, FEATURE_ADVANCED_GRADING, false)
 885              and has_capability('moodle/grade:managegradingforms', $context)) {
 886          require_once($CFG->dirroot.'/grade/grading/lib.php');
 887  
 888          $data->_advancedgradingdata['methods'] = grading_manager::available_methods();
 889          $areas = grading_manager::available_areas('mod_'.$module->name);
 890  
 891          foreach ($areas as $areaname => $areatitle) {
 892              $data->_advancedgradingdata['areas'][$areaname] = array(
 893                  'title'  => $areatitle,
 894                  'method' => '',
 895              );
 896              $formfield = 'advancedgradingmethod_'.$areaname;
 897              $data->{$formfield} = '';
 898          }
 899      }
 900  
 901      return array($module, $context, $cw, $cm, $data);
 902  }