Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
/course/ -> modlib.php (source)

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