Search moodle.org's
Developer Documentation


/course/ -> modlib.php (source)
   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  require_once($CFG->dirroot.'/course/lib.php');
  31  
  32  /**
  33   * Add course module.
  34   *
  35   * The function does not check user capabilities.
  36   * The function creates course module, module instance, add the module to the correct section.
  37   * It also trigger common action that need to be done after adding/updating a module.
  38   *
  39   * @param object $moduleinfo the moudle data
  40   * @param object $course the course of the module
  41   * @param object $mform this is required by an existing hack to deal with files during MODULENAME_add_instance()
  42   * @return object the updated module info
  43   */
  44  function add_moduleinfo($moduleinfo, $course, $mform = null) {
  45      global $DB, $CFG;
  46  
  47      // Attempt to include module library before we make any changes to DB.
  48      include_modulelib($moduleinfo->modulename);
  49  
  50      $moduleinfo->course = $course->id;
  51      $moduleinfo = set_moduleinfo_defaults($moduleinfo);
  52  
  53      if (!empty($course->groupmodeforce) or !isset($moduleinfo->groupmode)) {
  54          $moduleinfo->groupmode = 0; // Do not set groupmode.
  55      }
  56  
  57      // First add course_module record because we need the context.
  58      $newcm = new stdClass();
  59      $newcm->course           = $course->id;
  60      $newcm->module           = $moduleinfo->module;
  61      $newcm->instance         = 0; // Not known yet, will be updated later (this is similar to restore code).
  62      $newcm->visible          = $moduleinfo->visible;
  63      $newcm->visibleold       = $moduleinfo->visible;
  64      if (isset($moduleinfo->cmidnumber)) {
  65          $newcm->idnumber         = $moduleinfo->cmidnumber;
  66      }
  67      $newcm->groupmode        = $moduleinfo->groupmode;
  68      $newcm->groupingid       = $moduleinfo->groupingid;
  69      $completion = new completion_info($course);
  70      if ($completion->is_enabled()) {
  71          $newcm->completion                = $moduleinfo->completion;
  72          $newcm->completiongradeitemnumber = $moduleinfo->completiongradeitemnumber;
  73          $newcm->completionview            = $moduleinfo->completionview;
  74          $newcm->completionexpected        = $moduleinfo->completionexpected;
  75      }
  76      if(!empty($CFG->enableavailability)) {
  77          // This code is used both when submitting the form, which uses a long
  78          // name to avoid clashes, and by unit test code which uses the real
  79          // name in the table.
  80          $newcm->availability = null;
  81          if (property_exists($moduleinfo, 'availabilityconditionsjson')) {
  82              if ($moduleinfo->availabilityconditionsjson !== '') {
  83                  $newcm->availability = $moduleinfo->availabilityconditionsjson;
  84              }
  85          } else if (property_exists($moduleinfo, 'availability')) {
  86              $newcm->availability = $moduleinfo->availability;
  87          }
  88          // If there is any availability data, verify it.
  89          if ($newcm->availability) {
  90              $tree = new \core_availability\tree(json_decode($newcm->availability));
  91              // Save time and database space by setting null if the only data
  92              // is an empty tree.
  93              if ($tree->is_empty()) {
  94                  $newcm->availability = null;
  95              }
  96          }
  97      }
  98      if (isset($moduleinfo->showdescription)) {
  99          $newcm->showdescription = $moduleinfo->showdescription;
 100      } else {
 101          $newcm->showdescription = 0;
 102      }
 103  
 104      // From this point we make database changes, so start transaction.
 105      $transaction = $DB->start_delegated_transaction();
 106  
 107      if (!$moduleinfo->coursemodule = add_course_module($newcm)) {
 108          print_error('cannotaddcoursemodule');
 109      }
 110  
 111      if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true) &&
 112              isset($moduleinfo->introeditor)) {
 113          $introeditor = $moduleinfo->introeditor;
 114          unset($moduleinfo->introeditor);
 115          $moduleinfo->intro       = $introeditor['text'];
 116          $moduleinfo->introformat = $introeditor['format'];
 117      }
 118  
 119      $addinstancefunction    = $moduleinfo->modulename."_add_instance";
 120      try {
 121          $returnfromfunc = $addinstancefunction($moduleinfo, $mform);
 122      } catch (moodle_exception $e) {
 123          $returnfromfunc = $e;
 124      }
 125      if (!$returnfromfunc or !is_number($returnfromfunc)) {
 126          // Undo everything we can. This is not necessary for databases which
 127          // support transactions, but improves consistency for other databases.
 128          $modcontext = context_module::instance($moduleinfo->coursemodule);
 129          context_helper::delete_instance(CONTEXT_MODULE, $moduleinfo->coursemodule);
 130          $DB->delete_records('course_modules', array('id'=>$moduleinfo->coursemodule));
 131  
 132          if ($e instanceof moodle_exception) {
 133              throw $e;
 134          } else if (!is_number($returnfromfunc)) {
 135              print_error('invalidfunction', '', course_get_url($course, $moduleinfo->section));
 136          } else {
 137              print_error('cannotaddnewmodule', '', course_get_url($course, $moduleinfo->section), $moduleinfo->modulename);
 138          }
 139      }
 140  
 141      $moduleinfo->instance = $returnfromfunc;
 142  
 143      $DB->set_field('course_modules', 'instance', $returnfromfunc, array('id'=>$moduleinfo->coursemodule));
 144  
 145      // Update embedded links and save files.
 146      $modcontext = context_module::instance($moduleinfo->coursemodule);
 147      if (!empty($introeditor)) {
 148          $moduleinfo->intro = file_save_draft_area_files($introeditor['itemid'], $modcontext->id,
 149                                                        'mod_'.$moduleinfo->modulename, 'intro', 0,
 150                                                        array('subdirs'=>true), $introeditor['text']);
 151          $DB->set_field($moduleinfo->modulename, 'intro', $moduleinfo->intro, array('id'=>$moduleinfo->instance));
 152      }
 153  
 154      // Course_modules and course_sections each contain a reference to each other.
 155      // So we have to update one of them twice.
 156      $sectionid = course_add_cm_to_section($course, $moduleinfo->coursemodule, $moduleinfo->section);
 157  
 158      // Trigger event based on the action we did.
 159      // Api create_from_cm expects modname and id property, and we don't want to modify $moduleinfo since we are returning it.
 160      $eventdata = clone $moduleinfo;
 161      $eventdata->modname = $eventdata->modulename;
 162      $eventdata->id = $eventdata->coursemodule;
 163      $event = \core\event\course_module_created::create_from_cm($eventdata, $modcontext);
 164      $event->trigger();
 165  
 166      $moduleinfo = edit_module_post_actions($moduleinfo, $course);
 167      $transaction->allow_commit();
 168  
 169      return $moduleinfo;
 170  }
 171  
 172  
 173  /**
 174   * Common create/update module module actions that need to be processed as soon as a module is created/updaded.
 175   * For example:create grade parent category, add outcomes, rebuild caches, regrade, save plagiarism settings...
 176   * Please note this api does not trigger events as of MOODLE 2.6. Please trigger events before calling this api.
 177   *
 178   * @param object $moduleinfo the module info
 179   * @param object $course the course of the module
 180   *
 181   * @return object moduleinfo update with grading management info
 182   */
 183  function edit_module_post_actions($moduleinfo, $course) {
 184      global $CFG;
 185      require_once($CFG->libdir.'/gradelib.php');
 186  
 187      $modcontext = context_module::instance($moduleinfo->coursemodule);
 188      $hasgrades = plugin_supports('mod', $moduleinfo->modulename, FEATURE_GRADE_HAS_GRADE, false);
 189      $hasoutcomes = plugin_supports('mod', $moduleinfo->modulename, FEATURE_GRADE_OUTCOMES, true);
 190  
 191      // Sync idnumber with grade_item.
 192      if ($hasgrades && $grade_item = grade_item::fetch(array('itemtype'=>'mod', 'itemmodule'=>$moduleinfo->modulename,
 193                   'iteminstance'=>$moduleinfo->instance, 'itemnumber'=>0, 'courseid'=>$course->id))) {
 194          if ($grade_item->idnumber != $moduleinfo->cmidnumber) {
 195              $grade_item->idnumber = $moduleinfo->cmidnumber;
 196              $grade_item->update();
 197          }
 198      }
 199  
 200      if ($hasgrades) {
 201          $items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$moduleinfo->modulename,
 202                                           'iteminstance'=>$moduleinfo->instance, 'courseid'=>$course->id));
 203      } else {
 204          $items = array();
 205      }
 206  
 207      // Create parent category if requested and move to correct parent category.
 208      if ($items and isset($moduleinfo->gradecat)) {
 209          if ($moduleinfo->gradecat == -1) {
 210              $grade_category = new grade_category();
 211              $grade_category->courseid = $course->id;
 212              $grade_category->fullname = $moduleinfo->name;
 213              $grade_category->insert();
 214              if ($grade_item) {
 215                  $parent = $grade_item->get_parent_category();
 216                  $grade_category->set_parent($parent->id);
 217              }
 218              $moduleinfo->gradecat = $grade_category->id;
 219          }
 220  
 221          foreach ($items as $itemid=>$unused) {
 222              $items[$itemid]->set_parent($moduleinfo->gradecat);
 223              if ($itemid == $grade_item->id) {
 224                  // Use updated grade_item.
 225                  $grade_item = $items[$itemid];
 226              }
 227          }
 228      }
 229  
 230      require_once($CFG->libdir.'/grade/grade_outcome.php');
 231      // Add outcomes if requested.
 232      if ($hasoutcomes && $outcomes = grade_outcome::fetch_all_available($course->id)) {
 233          $grade_items = array();
 234  
 235          // Outcome grade_item.itemnumber start at 1000, there is nothing above outcomes.
 236          $max_itemnumber = 999;
 237          if ($items) {
 238              foreach($items as $item) {
 239                  if ($item->itemnumber > $max_itemnumber) {
 240                      $max_itemnumber = $item->itemnumber;
 241                  }
 242              }
 243          }
 244  
 245          foreach($outcomes as $outcome) {
 246              $elname = 'outcome_'.$outcome->id;
 247  
 248              if (property_exists($moduleinfo, $elname) and $moduleinfo->$elname) {
 249                  // So we have a request for new outcome grade item?
 250                  if ($items) {
 251                      $outcomeexists = false;
 252                      foreach($items as $item) {
 253                          if ($item->outcomeid == $outcome->id) {
 254                              $outcomeexists = true;
 255                              break;
 256                          }
 257                      }
 258                      if ($outcomeexists) {
 259                          continue;
 260                      }
 261                  }
 262  
 263                  $max_itemnumber++;
 264  
 265                  $outcome_item = new grade_item();
 266                  $outcome_item->courseid     = $course->id;
 267                  $outcome_item->itemtype     = 'mod';
 268                  $outcome_item->itemmodule   = $moduleinfo->modulename;
 269                  $outcome_item->iteminstance = $moduleinfo->instance;
 270                  $outcome_item->itemnumber   = $max_itemnumber;
 271                  $outcome_item->itemname     = $outcome->fullname;
 272                  $outcome_item->outcomeid    = $outcome->id;
 273                  $outcome_item->gradetype    = GRADE_TYPE_SCALE;
 274                  $outcome_item->scaleid      = $outcome->scaleid;
 275                  $outcome_item->insert();
 276  
 277                  // Move the new outcome into correct category and fix sortorder if needed.
 278                  if ($grade_item) {
 279                      $outcome_item->set_parent($grade_item->categoryid);
 280                      $outcome_item->move_after_sortorder($grade_item->sortorder);
 281  
 282                  } else if (isset($moduleinfo->gradecat)) {
 283                      $outcome_item->set_parent($moduleinfo->gradecat);
 284                  }
 285              }
 286          }
 287      }
 288  
 289      if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_ADVANCED_GRADING, false)
 290              and has_capability('moodle/grade:managegradingforms', $modcontext)) {
 291          require_once($CFG->dirroot.'/grade/grading/lib.php');
 292          $gradingman = get_grading_manager($modcontext, 'mod_'.$moduleinfo->modulename);
 293          $showgradingmanagement = false;
 294          foreach ($gradingman->get_available_areas() as $areaname => $aretitle) {
 295              $formfield = 'advancedgradingmethod_'.$areaname;
 296              if (isset($moduleinfo->{$formfield})) {
 297                  $gradingman->set_area($areaname);
 298                  $methodchanged = $gradingman->set_active_method($moduleinfo->{$formfield});
 299                  if (empty($moduleinfo->{$formfield})) {
 300                      // Going back to the simple direct grading is not a reason to open the management screen.
 301                      $methodchanged = false;
 302                  }
 303                  $showgradingmanagement = $showgradingmanagement || $methodchanged;
 304              }
 305          }
 306          // Update grading management information.
 307          $moduleinfo->gradingman = $gradingman;
 308          $moduleinfo->showgradingmanagement = $showgradingmanagement;
 309      }
 310  
 311      rebuild_course_cache($course->id, true);
 312      if ($hasgrades) {
 313          grade_regrade_final_grades($course->id);
 314      }
 315      require_once($CFG->libdir.'/plagiarismlib.php');
 316      plagiarism_save_form_elements($moduleinfo);
 317  
 318      return $moduleinfo;
 319  }
 320  
 321  
 322  /**
 323   * Set module info default values for the unset module attributs.
 324   *
 325   * @param object $moduleinfo the current known data of the module
 326   * @return object the completed module info
 327   */
 328  function set_moduleinfo_defaults($moduleinfo) {
 329  
 330      if (empty($moduleinfo->coursemodule)) {
 331          // Add.
 332          $cm = null;
 333          $moduleinfo->instance     = '';
 334          $moduleinfo->coursemodule = '';
 335      } else {
 336          // Update.
 337          $cm = get_coursemodule_from_id('', $moduleinfo->coursemodule, 0, false, MUST_EXIST);
 338          $moduleinfo->instance     = $cm->instance;
 339          $moduleinfo->coursemodule = $cm->id;
 340      }
 341      // For safety.
 342      $moduleinfo->modulename = clean_param($moduleinfo->modulename, PARAM_PLUGIN);
 343  
 344      if (!isset($moduleinfo->groupingid)) {
 345          $moduleinfo->groupingid = 0;
 346      }
 347  
 348      if (!isset($moduleinfo->name)) { // Label.
 349          $moduleinfo->name = $moduleinfo->modulename;
 350      }
 351  
 352      if (!isset($moduleinfo->completion)) {
 353          $moduleinfo->completion = COMPLETION_DISABLED;
 354      }
 355      if (!isset($moduleinfo->completionview)) {
 356          $moduleinfo->completionview = COMPLETION_VIEW_NOT_REQUIRED;
 357      }
 358      if (!isset($moduleinfo->completionexpected)) {
 359          $moduleinfo->completionexpected = 0;
 360      }
 361  
 362      // Convert the 'use grade' checkbox into a grade-item number: 0 if checked, null if not.
 363      if (isset($moduleinfo->completionusegrade) && $moduleinfo->completionusegrade) {
 364          $moduleinfo->completiongradeitemnumber = 0;
 365      } else {
 366          $moduleinfo->completiongradeitemnumber = null;
 367      }
 368  
 369      if (!isset($moduleinfo->conditiongradegroup)) {
 370          $moduleinfo->conditiongradegroup = array();
 371      }
 372      if (!isset($moduleinfo->conditionfieldgroup)) {
 373          $moduleinfo->conditionfieldgroup = array();
 374      }
 375  
 376      return $moduleinfo;
 377  }
 378  
 379  /**
 380   * Check that the user can add a module. Also returns some information like the module, context and course section info.
 381   * The fucntion create the course section if it doesn't exist.
 382   *
 383   * @param object $course the course of the module
 384   * @param object $modulename the module name
 385   * @param object $section the section of the module
 386   * @return array list containing module, context, course section.
 387   * @throws moodle_exception if user is not allowed to perform the action or module is not allowed in this course
 388   */
 389  function can_add_moduleinfo($course, $modulename, $section) {
 390      global $DB;
 391  
 392      $module = $DB->get_record('modules', array('name'=>$modulename), '*', MUST_EXIST);
 393  
 394      $context = context_course::instance($course->id);
 395      require_capability('moodle/course:manageactivities', $context);
 396  
 397      course_create_sections_if_missing($course, $section);
 398      $cw = get_fast_modinfo($course)->get_section_info($section);
 399  
 400      if (!course_allowed_module($course, $module->name)) {
 401          print_error('moduledisable');
 402      }
 403  
 404      return array($module, $context, $cw);
 405  }
 406  
 407  /**
 408   * Check if user is allowed to update module info and returns related item/data to the module.
 409   *
 410   * @param object $cm course module
 411   * @return array - list of course module, context, module, moduleinfo, and course section.
 412   * @throws moodle_exception if user is not allowed to perform the action
 413   */
 414  function can_update_moduleinfo($cm) {
 415      global $DB;
 416  
 417      // Check the $USER has the right capability.
 418      $context = context_module::instance($cm->id);
 419      require_capability('moodle/course:manageactivities', $context);
 420  
 421      // Check module exists.
 422      $module = $DB->get_record('modules', array('id'=>$cm->module), '*', MUST_EXIST);
 423  
 424      // Check the moduleinfo exists.
 425      $data = $DB->get_record($module->name, array('id'=>$cm->instance), '*', MUST_EXIST);
 426  
 427      // Check the course section exists.
 428      $cw = $DB->get_record('course_sections', array('id'=>$cm->section), '*', MUST_EXIST);
 429  
 430      return array($cm, $context, $module, $data, $cw);
 431  }
 432  
 433  
 434  /**
 435   * Update the module info.
 436   * This function doesn't check the user capabilities. It updates the course module and the module instance.
 437   * Then execute common action to create/update module process (trigger event, rebuild cache, save plagiarism settings...).
 438   *
 439   * @param object $cm course module
 440   * @param object $moduleinfo module info
 441   * @param object $course course of the module
 442   * @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.
 443   * @return array list of course module and module info.
 444   */
 445  function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
 446      global $DB, $CFG;
 447  
 448      // Attempt to include module library before we make any changes to DB.
 449      include_modulelib($moduleinfo->modulename);
 450  
 451      $moduleinfo->course = $course->id;
 452      $moduleinfo = set_moduleinfo_defaults($moduleinfo);
 453  
 454      if (!empty($course->groupmodeforce) or !isset($moduleinfo->groupmode)) {
 455          $moduleinfo->groupmode = $cm->groupmode; // Keep original.
 456      }
 457  
 458      // Update course module first.
 459      $cm->groupmode = $moduleinfo->groupmode;
 460      if (isset($moduleinfo->groupingid)) {
 461          $cm->groupingid = $moduleinfo->groupingid;
 462      }
 463  
 464      $completion = new completion_info($course);
 465      if ($completion->is_enabled()) {
 466          // Completion settings that would affect users who have already completed
 467          // the activity may be locked; if so, these should not be updated.
 468          if (!empty($moduleinfo->completionunlocked)) {
 469              $cm->completion = $moduleinfo->completion;
 470              $cm->completiongradeitemnumber = $moduleinfo->completiongradeitemnumber;
 471              $cm->completionview = $moduleinfo->completionview;
 472          }
 473          // The expected date does not affect users who have completed the activity,
 474          // so it is safe to update it regardless of the lock status.
 475          $cm->completionexpected = $moduleinfo->completionexpected;
 476      }
 477      if (!empty($CFG->enableavailability)) {
 478          // This code is used both when submitting the form, which uses a long
 479          // name to avoid clashes, and by unit test code which uses the real
 480          // name in the table.
 481          if (property_exists($moduleinfo, 'availabilityconditionsjson')) {
 482              if ($moduleinfo->availabilityconditionsjson !== '') {
 483                  $cm->availability = $moduleinfo->availabilityconditionsjson;
 484              } else {
 485                  $cm->availability = null;
 486              }
 487          } else if (property_exists($moduleinfo, 'availability')) {
 488              $cm->availability = $moduleinfo->availability;
 489          }
 490          // If there is any availability data, verify it.
 491          if ($cm->availability) {
 492              $tree = new \core_availability\tree(json_decode($cm->availability));
 493              // Save time and database space by setting null if the only data
 494              // is an empty tree.
 495              if ($tree->is_empty()) {
 496                  $cm->availability = null;
 497              }
 498          }
 499      }
 500      if (isset($moduleinfo->showdescription)) {
 501          $cm->showdescription = $moduleinfo->showdescription;
 502      } else {
 503          $cm->showdescription = 0;
 504      }
 505  
 506      $DB->update_record('course_modules', $cm);
 507  
 508      $modcontext = context_module::instance($moduleinfo->coursemodule);
 509  
 510      // Update embedded links and save files.
 511      if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) {
 512          $moduleinfo->intro = file_save_draft_area_files($moduleinfo->introeditor['itemid'], $modcontext->id,
 513                                                        'mod_'.$moduleinfo->modulename, 'intro', 0,
 514                                                        array('subdirs'=>true), $moduleinfo->introeditor['text']);
 515          $moduleinfo->introformat = $moduleinfo->introeditor['format'];
 516          unset($moduleinfo->introeditor);
 517      }
 518      $updateinstancefunction = $moduleinfo->modulename."_update_instance";
 519      if (!$updateinstancefunction($moduleinfo, $mform)) {
 520          print_error('cannotupdatemod', '', course_get_url($course, $cw->section), $moduleinfo->modulename);
 521      }
 522  
 523      // Make sure visibility is set correctly (in particular in calendar).
 524      if (has_capability('moodle/course:activityvisibility', $modcontext)) {
 525          set_coursemodule_visible($moduleinfo->coursemodule, $moduleinfo->visible);
 526      }
 527  
 528      if (isset($moduleinfo->cmidnumber)) { // Label.
 529          // Set cm idnumber - uniqueness is already verified by form validation.
 530          set_coursemodule_idnumber($moduleinfo->coursemodule, $moduleinfo->cmidnumber);
 531      }
 532  
 533      // Now that module is fully updated, also update completion data if required.
 534      // (this will wipe all user completion data and recalculate it)
 535      if ($completion->is_enabled() && !empty($moduleinfo->completionunlocked)) {
 536          $completion->reset_all_state($cm);
 537      }
 538      $cm->name = $moduleinfo->name;
 539      \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
 540  
 541      $moduleinfo = edit_module_post_actions($moduleinfo, $course);
 542  
 543      return array($cm, $moduleinfo);
 544  }
 545  
 546  /**
 547   * Include once the module lib file.
 548   *
 549   * @param string $modulename module name of the lib to include
 550   * @throws moodle_exception if lib.php file for the module does not exist
 551   */
 552  function include_modulelib($modulename) {
 553      global $CFG;
 554      $modlib = "$CFG->dirroot/mod/$modulename/lib.php";
 555      if (file_exists($modlib)) {
 556          include_once($modlib);
 557      } else {
 558          throw new moodle_exception('modulemissingcode', '', '', $modlib);
 559      }
 560  }
 561  

Search This Site: