See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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 /** 19 * External course API 20 * 21 * @package core_course 22 * @category external 23 * @copyright 2009 Petr Skodak 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 */ 26 27 defined('MOODLE_INTERNAL') || die; 28 29 use core_course\external\course_summary_exporter; 30 31 require_once("$CFG->libdir/externallib.php"); 32 require_once (__DIR__ . "/lib.php"); 33 34 /** 35 * Course external functions 36 * 37 * @package core_course 38 * @category external 39 * @copyright 2011 Jerome Mouneyrac 40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 41 * @since Moodle 2.2 42 */ 43 class core_course_external extends external_api { 44 45 /** 46 * Returns description of method parameters 47 * 48 * @return external_function_parameters 49 * @since Moodle 2.9 Options available 50 * @since Moodle 2.2 51 */ 52 public static function get_course_contents_parameters() { 53 return new external_function_parameters( 54 array('courseid' => new external_value(PARAM_INT, 'course id'), 55 'options' => new external_multiple_structure ( 56 new external_single_structure( 57 array( 58 'name' => new external_value(PARAM_ALPHANUM, 59 'The expected keys (value format) are: 60 excludemodules (bool) Do not return modules, return only the sections structure 61 excludecontents (bool) Do not return module contents (i.e: files inside a resource) 62 includestealthmodules (bool) Return stealth modules for students in a special 63 section (with id -1) 64 sectionid (int) Return only this section 65 sectionnumber (int) Return only this section with number (order) 66 cmid (int) Return only this module information (among the whole sections structure) 67 modname (string) Return only modules with this name "label, forum, etc..." 68 modid (int) Return only the module with this id (to be used with modname'), 69 'value' => new external_value(PARAM_RAW, 'the value of the option, 70 this param is personaly validated in the external function.') 71 ) 72 ), 'Options, used since Moodle 2.9', VALUE_DEFAULT, array()) 73 ) 74 ); 75 } 76 77 /** 78 * Get course contents 79 * 80 * @param int $courseid course id 81 * @param array $options Options for filtering the results, used since Moodle 2.9 82 * @return array 83 * @since Moodle 2.9 Options available 84 * @since Moodle 2.2 85 */ 86 public static function get_course_contents($courseid, $options = array()) { 87 global $CFG, $DB; 88 require_once($CFG->dirroot . "/course/lib.php"); 89 require_once($CFG->libdir . '/completionlib.php'); 90 91 //validate parameter 92 $params = self::validate_parameters(self::get_course_contents_parameters(), 93 array('courseid' => $courseid, 'options' => $options)); 94 95 $filters = array(); 96 if (!empty($params['options'])) { 97 98 foreach ($params['options'] as $option) { 99 $name = trim($option['name']); 100 // Avoid duplicated options. 101 if (!isset($filters[$name])) { 102 switch ($name) { 103 case 'excludemodules': 104 case 'excludecontents': 105 case 'includestealthmodules': 106 $value = clean_param($option['value'], PARAM_BOOL); 107 $filters[$name] = $value; 108 break; 109 case 'sectionid': 110 case 'sectionnumber': 111 case 'cmid': 112 case 'modid': 113 $value = clean_param($option['value'], PARAM_INT); 114 if (is_numeric($value)) { 115 $filters[$name] = $value; 116 } else { 117 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name); 118 } 119 break; 120 case 'modname': 121 $value = clean_param($option['value'], PARAM_PLUGIN); 122 if ($value) { 123 $filters[$name] = $value; 124 } else { 125 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name); 126 } 127 break; 128 default: 129 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name); 130 } 131 } 132 } 133 } 134 135 //retrieve the course 136 $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST); 137 138 if ($course->id != SITEID) { 139 // Check course format exist. 140 if (!file_exists($CFG->dirroot . '/course/format/' . $course->format . '/lib.php')) { 141 throw new moodle_exception('cannotgetcoursecontents', 'webservice', '', null, 142 get_string('courseformatnotfound', 'error', $course->format)); 143 } else { 144 require_once($CFG->dirroot . '/course/format/' . $course->format . '/lib.php'); 145 } 146 } 147 148 // now security checks 149 $context = context_course::instance($course->id, IGNORE_MISSING); 150 try { 151 self::validate_context($context); 152 } catch (Exception $e) { 153 $exceptionparam = new stdClass(); 154 $exceptionparam->message = $e->getMessage(); 155 $exceptionparam->courseid = $course->id; 156 throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam); 157 } 158 159 $canupdatecourse = has_capability('moodle/course:update', $context); 160 161 //create return value 162 $coursecontents = array(); 163 164 if ($canupdatecourse or $course->visible 165 or has_capability('moodle/course:viewhiddencourses', $context)) { 166 167 //retrieve sections 168 $modinfo = get_fast_modinfo($course); 169 $sections = $modinfo->get_section_info_all(); 170 $courseformat = course_get_format($course); 171 $coursenumsections = $courseformat->get_last_section_number(); 172 $stealthmodules = array(); // Array to keep all the modules available but not visible in a course section/topic. 173 174 $completioninfo = new completion_info($course); 175 176 //for each sections (first displayed to last displayed) 177 $modinfosections = $modinfo->get_sections(); 178 foreach ($sections as $key => $section) { 179 180 // This becomes true when we are filtering and we found the value to filter with. 181 $sectionfound = false; 182 183 // Filter by section id. 184 if (!empty($filters['sectionid'])) { 185 if ($section->id != $filters['sectionid']) { 186 continue; 187 } else { 188 $sectionfound = true; 189 } 190 } 191 192 // Filter by section number. Note that 0 is a valid section number. 193 if (isset($filters['sectionnumber'])) { 194 if ($key != $filters['sectionnumber']) { 195 continue; 196 } else { 197 $sectionfound = true; 198 } 199 } 200 201 // reset $sectioncontents 202 $sectionvalues = array(); 203 $sectionvalues['id'] = $section->id; 204 $sectionvalues['name'] = get_section_name($course, $section); 205 $sectionvalues['visible'] = $section->visible; 206 207 $options = (object) array('noclean' => true); 208 list($sectionvalues['summary'], $sectionvalues['summaryformat']) = 209 external_format_text($section->summary, $section->summaryformat, 210 $context->id, 'course', 'section', $section->id, $options); 211 $sectionvalues['section'] = $section->section; 212 $sectionvalues['hiddenbynumsections'] = $section->section > $coursenumsections ? 1 : 0; 213 $sectionvalues['uservisible'] = $section->uservisible; 214 if (!empty($section->availableinfo)) { 215 $sectionvalues['availabilityinfo'] = \core_availability\info::format_info($section->availableinfo, $course); 216 } 217 218 $sectioncontents = array(); 219 220 // For each module of the section. 221 if (empty($filters['excludemodules']) and !empty($modinfosections[$section->section])) { 222 foreach ($modinfosections[$section->section] as $cmid) { 223 $cm = $modinfo->cms[$cmid]; 224 225 // Stop here if the module is not visible to the user on the course main page: 226 // The user can't access the module and the user can't view the module on the course page. 227 if (!$cm->uservisible && !$cm->is_visible_on_course_page()) { 228 continue; 229 } 230 231 // This becomes true when we are filtering and we found the value to filter with. 232 $modfound = false; 233 234 // Filter by cmid. 235 if (!empty($filters['cmid'])) { 236 if ($cmid != $filters['cmid']) { 237 continue; 238 } else { 239 $modfound = true; 240 } 241 } 242 243 // Filter by module name and id. 244 if (!empty($filters['modname'])) { 245 if ($cm->modname != $filters['modname']) { 246 continue; 247 } else if (!empty($filters['modid'])) { 248 if ($cm->instance != $filters['modid']) { 249 continue; 250 } else { 251 // Note that if we are only filtering by modname we don't break the loop. 252 $modfound = true; 253 } 254 } 255 } 256 257 $module = array(); 258 259 $modcontext = context_module::instance($cm->id); 260 261 //common info (for people being able to see the module or availability dates) 262 $module['id'] = $cm->id; 263 $module['name'] = external_format_string($cm->name, $modcontext->id); 264 $module['instance'] = $cm->instance; 265 $module['modname'] = (string) $cm->modname; 266 $module['modplural'] = (string) $cm->modplural; 267 $module['modicon'] = $cm->get_icon_url()->out(false); 268 $module['indent'] = $cm->indent; 269 $module['onclick'] = $cm->onclick; 270 $module['afterlink'] = $cm->afterlink; 271 $module['customdata'] = json_encode($cm->customdata); 272 $module['completion'] = $cm->completion; 273 $module['noviewlink'] = plugin_supports('mod', $cm->modname, FEATURE_NO_VIEW_LINK, false); 274 275 // Check module completion. 276 $completion = $completioninfo->is_enabled($cm); 277 if ($completion != COMPLETION_DISABLED) { 278 $completiondata = $completioninfo->get_data($cm, true); 279 $module['completiondata'] = array( 280 'state' => $completiondata->completionstate, 281 'timecompleted' => $completiondata->timemodified, 282 'overrideby' => $completiondata->overrideby, 283 'valueused' => core_availability\info::completion_value_used($course, $cm->id) 284 ); 285 } 286 287 if (!empty($cm->showdescription) or $module['noviewlink']) { 288 // We want to use the external format. However from reading get_formatted_content(), $cm->content format is always FORMAT_HTML. 289 $options = array('noclean' => true); 290 list($module['description'], $descriptionformat) = external_format_text($cm->content, 291 FORMAT_HTML, $modcontext->id, $cm->modname, 'intro', $cm->id, $options); 292 } 293 294 //url of the module 295 $url = $cm->url; 296 if ($url) { //labels don't have url 297 $module['url'] = $url->out(false); 298 } 299 300 $canviewhidden = has_capability('moodle/course:viewhiddenactivities', 301 context_module::instance($cm->id)); 302 //user that can view hidden module should know about the visibility 303 $module['visible'] = $cm->visible; 304 $module['visibleoncoursepage'] = $cm->visibleoncoursepage; 305 $module['uservisible'] = $cm->uservisible; 306 if (!empty($cm->availableinfo)) { 307 $module['availabilityinfo'] = \core_availability\info::format_info($cm->availableinfo, $course); 308 } 309 310 // Availability date (also send to user who can see hidden module). 311 if ($CFG->enableavailability && ($canviewhidden || $canupdatecourse)) { 312 $module['availability'] = $cm->availability; 313 } 314 315 // Return contents only if the user can access to the module. 316 if ($cm->uservisible) { 317 $baseurl = 'webservice/pluginfile.php'; 318 319 // Call $modulename_export_contents (each module callback take care about checking the capabilities). 320 require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php'); 321 $getcontentfunction = $cm->modname.'_export_contents'; 322 if (function_exists($getcontentfunction)) { 323 $contents = $getcontentfunction($cm, $baseurl); 324 $module['contentsinfo'] = array( 325 'filescount' => count($contents), 326 'filessize' => 0, 327 'lastmodified' => 0, 328 'mimetypes' => array(), 329 ); 330 foreach ($contents as $content) { 331 // Check repository file (only main file). 332 if (!isset($module['contentsinfo']['repositorytype'])) { 333 $module['contentsinfo']['repositorytype'] = 334 isset($content['repositorytype']) ? $content['repositorytype'] : ''; 335 } 336 if (isset($content['filesize'])) { 337 $module['contentsinfo']['filessize'] += $content['filesize']; 338 } 339 if (isset($content['timemodified']) && 340 ($content['timemodified'] > $module['contentsinfo']['lastmodified'])) { 341 342 $module['contentsinfo']['lastmodified'] = $content['timemodified']; 343 } 344 if (isset($content['mimetype'])) { 345 $module['contentsinfo']['mimetypes'][$content['mimetype']] = $content['mimetype']; 346 } 347 } 348 349 if (empty($filters['excludecontents']) and !empty($contents)) { 350 $module['contents'] = $contents; 351 } else { 352 $module['contents'] = array(); 353 } 354 } 355 } 356 357 // Assign result to $sectioncontents, there is an exception, 358 // stealth activities in non-visible sections for students go to a special section. 359 if (!empty($filters['includestealthmodules']) && !$section->uservisible && $cm->is_stealth()) { 360 $stealthmodules[] = $module; 361 } else { 362 $sectioncontents[] = $module; 363 } 364 365 // If we just did a filtering, break the loop. 366 if ($modfound) { 367 break; 368 } 369 370 } 371 } 372 $sectionvalues['modules'] = $sectioncontents; 373 374 // assign result to $coursecontents 375 $coursecontents[$key] = $sectionvalues; 376 377 // Break the loop if we are filtering. 378 if ($sectionfound) { 379 break; 380 } 381 } 382 383 // Now that we have iterated over all the sections and activities, check the visibility. 384 // We didn't this before to be able to retrieve stealth activities. 385 foreach ($coursecontents as $sectionnumber => $sectioncontents) { 386 $section = $sections[$sectionnumber]; 387 // Show the section if the user is permitted to access it OR 388 // if it's not available but there is some available info text which explains the reason & should display OR 389 // the course is configured to show hidden sections name. 390 $showsection = $section->uservisible || 391 ($section->visible && !$section->available && !empty($section->availableinfo)) || 392 (!$section->visible && empty($courseformat->get_course()->hiddensections)); 393 394 if (!$showsection) { 395 unset($coursecontents[$sectionnumber]); 396 continue; 397 } 398 399 // Remove section and modules information if the section is not visible for the user. 400 if (!$section->uservisible) { 401 $coursecontents[$sectionnumber]['modules'] = array(); 402 // Remove summary information if the section is completely hidden only, 403 // even if the section is not user visible, the summary is always displayed among the availability information. 404 if (!$section->visible) { 405 $coursecontents[$sectionnumber]['summary'] = ''; 406 } 407 } 408 } 409 410 // Include stealth modules in special section (without any info). 411 if (!empty($stealthmodules)) { 412 $coursecontents[] = array( 413 'id' => -1, 414 'name' => '', 415 'summary' => '', 416 'summaryformat' => FORMAT_MOODLE, 417 'modules' => $stealthmodules 418 ); 419 } 420 421 } 422 return $coursecontents; 423 } 424 425 /** 426 * Returns description of method result value 427 * 428 * @return external_description 429 * @since Moodle 2.2 430 */ 431 public static function get_course_contents_returns() { 432 return new external_multiple_structure( 433 new external_single_structure( 434 array( 435 'id' => new external_value(PARAM_INT, 'Section ID'), 436 'name' => new external_value(PARAM_RAW, 'Section name'), 437 'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL), 438 'summary' => new external_value(PARAM_RAW, 'Section description'), 439 'summaryformat' => new external_format_value('summary'), 440 'section' => new external_value(PARAM_INT, 'Section number inside the course', VALUE_OPTIONAL), 441 'hiddenbynumsections' => new external_value(PARAM_INT, 'Whether is a section hidden in the course format', 442 VALUE_OPTIONAL), 443 'uservisible' => new external_value(PARAM_BOOL, 'Is the section visible for the user?', VALUE_OPTIONAL), 444 'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.', VALUE_OPTIONAL), 445 'modules' => new external_multiple_structure( 446 new external_single_structure( 447 array( 448 'id' => new external_value(PARAM_INT, 'activity id'), 449 'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL), 450 'name' => new external_value(PARAM_RAW, 'activity module name'), 451 'instance' => new external_value(PARAM_INT, 'instance id', VALUE_OPTIONAL), 452 'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL), 453 'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL), 454 'uservisible' => new external_value(PARAM_BOOL, 'Is the module visible for the user?', 455 VALUE_OPTIONAL), 456 'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.', 457 VALUE_OPTIONAL), 458 'visibleoncoursepage' => new external_value(PARAM_INT, 'is the module visible on course page', 459 VALUE_OPTIONAL), 460 'modicon' => new external_value(PARAM_URL, 'activity icon url'), 461 'modname' => new external_value(PARAM_PLUGIN, 'activity module type'), 462 'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'), 463 'availability' => new external_value(PARAM_RAW, 'module availability settings', VALUE_OPTIONAL), 464 'indent' => new external_value(PARAM_INT, 'number of identation in the site'), 465 'onclick' => new external_value(PARAM_RAW, 'Onclick action.', VALUE_OPTIONAL), 466 'afterlink' => new external_value(PARAM_RAW, 'After link info to be displayed.', 467 VALUE_OPTIONAL), 468 'customdata' => new external_value(PARAM_RAW, 'Custom data (JSON encoded).', VALUE_OPTIONAL), 469 'noviewlink' => new external_value(PARAM_BOOL, 'Whether the module has no view page', 470 VALUE_OPTIONAL), 471 'completion' => new external_value(PARAM_INT, 'Type of completion tracking: 472 0 means none, 1 manual, 2 automatic.', VALUE_OPTIONAL), 473 'completiondata' => new external_single_structure( 474 array( 475 'state' => new external_value(PARAM_INT, 'Completion state value: 476 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail'), 477 'timecompleted' => new external_value(PARAM_INT, 'Timestamp for completion status.'), 478 'overrideby' => new external_value(PARAM_INT, 'The user id who has overriden the 479 status.'), 480 'valueused' => new external_value(PARAM_BOOL, 'Whether the completion status affects 481 the availability of another activity.', VALUE_OPTIONAL), 482 ), 'Module completion data.', VALUE_OPTIONAL 483 ), 484 'contents' => new external_multiple_structure( 485 new external_single_structure( 486 array( 487 // content info 488 'type'=> new external_value(PARAM_TEXT, 'a file or a folder or external link'), 489 'filename'=> new external_value(PARAM_FILE, 'filename'), 490 'filepath'=> new external_value(PARAM_PATH, 'filepath'), 491 'filesize'=> new external_value(PARAM_INT, 'filesize'), 492 'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL), 493 'content' => new external_value(PARAM_RAW, 'Raw content, will be used when type is content', VALUE_OPTIONAL), 494 'timecreated' => new external_value(PARAM_INT, 'Time created'), 495 'timemodified' => new external_value(PARAM_INT, 'Time modified'), 496 'sortorder' => new external_value(PARAM_INT, 'Content sort order'), 497 'mimetype' => new external_value(PARAM_RAW, 'File mime type.', VALUE_OPTIONAL), 498 'isexternalfile' => new external_value(PARAM_BOOL, 'Whether is an external file.', 499 VALUE_OPTIONAL), 500 'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for external files.', 501 VALUE_OPTIONAL), 502 503 // copyright related info 504 'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'), 505 'author' => new external_value(PARAM_TEXT, 'Content owner'), 506 'license' => new external_value(PARAM_TEXT, 'Content license'), 507 'tags' => new external_multiple_structure( 508 \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags', 509 VALUE_OPTIONAL 510 ), 511 ) 512 ), VALUE_DEFAULT, array() 513 ), 514 'contentsinfo' => new external_single_structure( 515 array( 516 'filescount' => new external_value(PARAM_INT, 'Total number of files.'), 517 'filessize' => new external_value(PARAM_INT, 'Total files size.'), 518 'lastmodified' => new external_value(PARAM_INT, 'Last time files were modified.'), 519 'mimetypes' => new external_multiple_structure( 520 new external_value(PARAM_RAW, 'File mime type.'), 521 'Files mime types.' 522 ), 523 'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for 524 the main file.', VALUE_OPTIONAL), 525 ), 'Contents summary information.', VALUE_OPTIONAL 526 ), 527 ) 528 ), 'list of module' 529 ) 530 ) 531 ) 532 ); 533 } 534 535 /** 536 * Returns description of method parameters 537 * 538 * @return external_function_parameters 539 * @since Moodle 2.3 540 */ 541 public static function get_courses_parameters() { 542 return new external_function_parameters( 543 array('options' => new external_single_structure( 544 array('ids' => new external_multiple_structure( 545 new external_value(PARAM_INT, 'Course id') 546 , 'List of course id. If empty return all courses 547 except front page course.', 548 VALUE_OPTIONAL) 549 ), 'options - operator OR is used', VALUE_DEFAULT, array()) 550 ) 551 ); 552 } 553 554 /** 555 * Get courses 556 * 557 * @param array $options It contains an array (list of ids) 558 * @return array 559 * @since Moodle 2.2 560 */ 561 public static function get_courses($options = array()) { 562 global $CFG, $DB; 563 require_once($CFG->dirroot . "/course/lib.php"); 564 565 //validate parameter 566 $params = self::validate_parameters(self::get_courses_parameters(), 567 array('options' => $options)); 568 569 //retrieve courses 570 if (!array_key_exists('ids', $params['options']) 571 or empty($params['options']['ids'])) { 572 $courses = $DB->get_records('course'); 573 } else { 574 $courses = $DB->get_records_list('course', 'id', $params['options']['ids']); 575 } 576 577 //create return value 578 $coursesinfo = array(); 579 foreach ($courses as $course) { 580 581 // now security checks 582 $context = context_course::instance($course->id, IGNORE_MISSING); 583 $courseformatoptions = course_get_format($course)->get_format_options(); 584 try { 585 self::validate_context($context); 586 } catch (Exception $e) { 587 $exceptionparam = new stdClass(); 588 $exceptionparam->message = $e->getMessage(); 589 $exceptionparam->courseid = $course->id; 590 throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam); 591 } 592 if ($course->id != SITEID) { 593 require_capability('moodle/course:view', $context); 594 } 595 596 $courseinfo = array(); 597 $courseinfo['id'] = $course->id; 598 $courseinfo['fullname'] = external_format_string($course->fullname, $context->id); 599 $courseinfo['shortname'] = external_format_string($course->shortname, $context->id); 600 $courseinfo['displayname'] = external_format_string(get_course_display_name_for_list($course), $context->id); 601 $courseinfo['categoryid'] = $course->category; 602 list($courseinfo['summary'], $courseinfo['summaryformat']) = 603 external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0); 604 $courseinfo['format'] = $course->format; 605 $courseinfo['startdate'] = $course->startdate; 606 $courseinfo['enddate'] = $course->enddate; 607 if (array_key_exists('numsections', $courseformatoptions)) { 608 // For backward-compartibility 609 $courseinfo['numsections'] = $courseformatoptions['numsections']; 610 } 611 612 $handler = core_course\customfield\course_handler::create(); 613 if ($customfields = $handler->export_instance_data($course->id)) { 614 $courseinfo['customfields'] = []; 615 foreach ($customfields as $data) { 616 $courseinfo['customfields'][] = [ 617 'type' => $data->get_type(), 618 'value' => $data->get_value(), 619 'name' => $data->get_name(), 620 'shortname' => $data->get_shortname() 621 ]; 622 } 623 } 624 625 //some field should be returned only if the user has update permission 626 $courseadmin = has_capability('moodle/course:update', $context); 627 if ($courseadmin) { 628 $courseinfo['categorysortorder'] = $course->sortorder; 629 $courseinfo['idnumber'] = $course->idnumber; 630 $courseinfo['showgrades'] = $course->showgrades; 631 $courseinfo['showreports'] = $course->showreports; 632 $courseinfo['newsitems'] = $course->newsitems; 633 $courseinfo['visible'] = $course->visible; 634 $courseinfo['maxbytes'] = $course->maxbytes; 635 if (array_key_exists('hiddensections', $courseformatoptions)) { 636 // For backward-compartibility 637 $courseinfo['hiddensections'] = $courseformatoptions['hiddensections']; 638 } 639 // Return numsections for backward-compatibility with clients who expect it. 640 $courseinfo['numsections'] = course_get_format($course)->get_last_section_number(); 641 $courseinfo['groupmode'] = $course->groupmode; 642 $courseinfo['groupmodeforce'] = $course->groupmodeforce; 643 $courseinfo['defaultgroupingid'] = $course->defaultgroupingid; 644 $courseinfo['lang'] = clean_param($course->lang, PARAM_LANG); 645 $courseinfo['timecreated'] = $course->timecreated; 646 $courseinfo['timemodified'] = $course->timemodified; 647 $courseinfo['forcetheme'] = clean_param($course->theme, PARAM_THEME); 648 $courseinfo['enablecompletion'] = $course->enablecompletion; 649 $courseinfo['completionnotify'] = $course->completionnotify; 650 $courseinfo['courseformatoptions'] = array(); 651 foreach ($courseformatoptions as $key => $value) { 652 $courseinfo['courseformatoptions'][] = array( 653 'name' => $key, 654 'value' => $value 655 ); 656 } 657 } 658 659 if ($courseadmin or $course->visible 660 or has_capability('moodle/course:viewhiddencourses', $context)) { 661 $coursesinfo[] = $courseinfo; 662 } 663 } 664 665 return $coursesinfo; 666 } 667 668 /** 669 * Returns description of method result value 670 * 671 * @return external_description 672 * @since Moodle 2.2 673 */ 674 public static function get_courses_returns() { 675 return new external_multiple_structure( 676 new external_single_structure( 677 array( 678 'id' => new external_value(PARAM_INT, 'course id'), 679 'shortname' => new external_value(PARAM_RAW, 'course short name'), 680 'categoryid' => new external_value(PARAM_INT, 'category id'), 681 'categorysortorder' => new external_value(PARAM_INT, 682 'sort order into the category', VALUE_OPTIONAL), 683 'fullname' => new external_value(PARAM_RAW, 'full name'), 684 'displayname' => new external_value(PARAM_RAW, 'course display name'), 685 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL), 686 'summary' => new external_value(PARAM_RAW, 'summary'), 687 'summaryformat' => new external_format_value('summary'), 688 'format' => new external_value(PARAM_PLUGIN, 689 'course format: weeks, topics, social, site,..'), 690 'showgrades' => new external_value(PARAM_INT, 691 '1 if grades are shown, otherwise 0', VALUE_OPTIONAL), 692 'newsitems' => new external_value(PARAM_INT, 693 'number of recent items appearing on the course page', VALUE_OPTIONAL), 694 'startdate' => new external_value(PARAM_INT, 695 'timestamp when the course start'), 696 'enddate' => new external_value(PARAM_INT, 697 'timestamp when the course end'), 698 'numsections' => new external_value(PARAM_INT, 699 '(deprecated, use courseformatoptions) number of weeks/topics', 700 VALUE_OPTIONAL), 701 'maxbytes' => new external_value(PARAM_INT, 702 'largest size of file that can be uploaded into the course', 703 VALUE_OPTIONAL), 704 'showreports' => new external_value(PARAM_INT, 705 'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL), 706 'visible' => new external_value(PARAM_INT, 707 '1: available to student, 0:not available', VALUE_OPTIONAL), 708 'hiddensections' => new external_value(PARAM_INT, 709 '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students', 710 VALUE_OPTIONAL), 711 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', 712 VALUE_OPTIONAL), 713 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', 714 VALUE_OPTIONAL), 715 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', 716 VALUE_OPTIONAL), 717 'timecreated' => new external_value(PARAM_INT, 718 'timestamp when the course have been created', VALUE_OPTIONAL), 719 'timemodified' => new external_value(PARAM_INT, 720 'timestamp when the course have been modified', VALUE_OPTIONAL), 721 'enablecompletion' => new external_value(PARAM_INT, 722 'Enabled, control via completion and activity settings. Disbaled, 723 not shown in activity settings.', 724 VALUE_OPTIONAL), 725 'completionnotify' => new external_value(PARAM_INT, 726 '1: yes 0: no', VALUE_OPTIONAL), 727 'lang' => new external_value(PARAM_SAFEDIR, 728 'forced course language', VALUE_OPTIONAL), 729 'forcetheme' => new external_value(PARAM_PLUGIN, 730 'name of the force theme', VALUE_OPTIONAL), 731 'courseformatoptions' => new external_multiple_structure( 732 new external_single_structure( 733 array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'), 734 'value' => new external_value(PARAM_RAW, 'course format option value') 735 )), 'additional options for particular course format', VALUE_OPTIONAL 736 ), 737 'customfields' => new external_multiple_structure( 738 new external_single_structure( 739 ['name' => new external_value(PARAM_RAW, 'The name of the custom field'), 740 'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'), 741 'type' => new external_value(PARAM_COMPONENT, 742 'The type of the custom field - text, checkbox...'), 743 'value' => new external_value(PARAM_RAW, 'The value of the custom field')] 744 ), 'Custom fields and associated values', VALUE_OPTIONAL), 745 ), 'course' 746 ) 747 ); 748 } 749 750 /** 751 * Returns description of method parameters 752 * 753 * @return external_function_parameters 754 * @since Moodle 2.2 755 */ 756 public static function create_courses_parameters() { 757 $courseconfig = get_config('moodlecourse'); //needed for many default values 758 return new external_function_parameters( 759 array( 760 'courses' => new external_multiple_structure( 761 new external_single_structure( 762 array( 763 'fullname' => new external_value(PARAM_TEXT, 'full name'), 764 'shortname' => new external_value(PARAM_TEXT, 'course short name'), 765 'categoryid' => new external_value(PARAM_INT, 'category id'), 766 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL), 767 'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL), 768 'summaryformat' => new external_format_value('summary', VALUE_DEFAULT), 769 'format' => new external_value(PARAM_PLUGIN, 770 'course format: weeks, topics, social, site,..', 771 VALUE_DEFAULT, $courseconfig->format), 772 'showgrades' => new external_value(PARAM_INT, 773 '1 if grades are shown, otherwise 0', VALUE_DEFAULT, 774 $courseconfig->showgrades), 775 'newsitems' => new external_value(PARAM_INT, 776 'number of recent items appearing on the course page', 777 VALUE_DEFAULT, $courseconfig->newsitems), 778 'startdate' => new external_value(PARAM_INT, 779 'timestamp when the course start', VALUE_OPTIONAL), 780 'enddate' => new external_value(PARAM_INT, 781 'timestamp when the course end', VALUE_OPTIONAL), 782 'numsections' => new external_value(PARAM_INT, 783 '(deprecated, use courseformatoptions) number of weeks/topics', 784 VALUE_OPTIONAL), 785 'maxbytes' => new external_value(PARAM_INT, 786 'largest size of file that can be uploaded into the course', 787 VALUE_DEFAULT, $courseconfig->maxbytes), 788 'showreports' => new external_value(PARAM_INT, 789 'are activity report shown (yes = 1, no =0)', VALUE_DEFAULT, 790 $courseconfig->showreports), 791 'visible' => new external_value(PARAM_INT, 792 '1: available to student, 0:not available', VALUE_OPTIONAL), 793 'hiddensections' => new external_value(PARAM_INT, 794 '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students', 795 VALUE_OPTIONAL), 796 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', 797 VALUE_DEFAULT, $courseconfig->groupmode), 798 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', 799 VALUE_DEFAULT, $courseconfig->groupmodeforce), 800 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', 801 VALUE_DEFAULT, 0), 802 'enablecompletion' => new external_value(PARAM_INT, 803 'Enabled, control via completion and activity settings. Disabled, 804 not shown in activity settings.', 805 VALUE_OPTIONAL), 806 'completionnotify' => new external_value(PARAM_INT, 807 '1: yes 0: no', VALUE_OPTIONAL), 808 'lang' => new external_value(PARAM_SAFEDIR, 809 'forced course language', VALUE_OPTIONAL), 810 'forcetheme' => new external_value(PARAM_PLUGIN, 811 'name of the force theme', VALUE_OPTIONAL), 812 'courseformatoptions' => new external_multiple_structure( 813 new external_single_structure( 814 array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'), 815 'value' => new external_value(PARAM_RAW, 'course format option value') 816 )), 817 'additional options for particular course format', VALUE_OPTIONAL), 818 'customfields' => new external_multiple_structure( 819 new external_single_structure( 820 array( 821 'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'), 822 'value' => new external_value(PARAM_RAW, 'The value of the custom field'), 823 )), 'custom fields for the course', VALUE_OPTIONAL 824 ) 825 )), 'courses to create' 826 ) 827 ) 828 ); 829 } 830 831 /** 832 * Create courses 833 * 834 * @param array $courses 835 * @return array courses (id and shortname only) 836 * @since Moodle 2.2 837 */ 838 public static function create_courses($courses) { 839 global $CFG, $DB; 840 require_once($CFG->dirroot . "/course/lib.php"); 841 require_once($CFG->libdir . '/completionlib.php'); 842 843 $params = self::validate_parameters(self::create_courses_parameters(), 844 array('courses' => $courses)); 845 846 $availablethemes = core_component::get_plugin_list('theme'); 847 $availablelangs = get_string_manager()->get_list_of_translations(); 848 849 $transaction = $DB->start_delegated_transaction(); 850 851 foreach ($params['courses'] as $course) { 852 853 // Ensure the current user is allowed to run this function 854 $context = context_coursecat::instance($course['categoryid'], IGNORE_MISSING); 855 try { 856 self::validate_context($context); 857 } catch (Exception $e) { 858 $exceptionparam = new stdClass(); 859 $exceptionparam->message = $e->getMessage(); 860 $exceptionparam->catid = $course['categoryid']; 861 throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam); 862 } 863 require_capability('moodle/course:create', $context); 864 865 // Fullname and short name are required to be non-empty. 866 if (trim($course['fullname']) === '') { 867 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname'); 868 } else if (trim($course['shortname']) === '') { 869 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname'); 870 } 871 872 // Make sure lang is valid 873 if (array_key_exists('lang', $course)) { 874 if (empty($availablelangs[$course['lang']])) { 875 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang'); 876 } 877 if (!has_capability('moodle/course:setforcedlanguage', $context)) { 878 unset($course['lang']); 879 } 880 } 881 882 // Make sure theme is valid 883 if (array_key_exists('forcetheme', $course)) { 884 if (!empty($CFG->allowcoursethemes)) { 885 if (empty($availablethemes[$course['forcetheme']])) { 886 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme'); 887 } else { 888 $course['theme'] = $course['forcetheme']; 889 } 890 } 891 } 892 893 //force visibility if ws user doesn't have the permission to set it 894 $category = $DB->get_record('course_categories', array('id' => $course['categoryid'])); 895 if (!has_capability('moodle/course:visibility', $context)) { 896 $course['visible'] = $category->visible; 897 } 898 899 //set default value for completion 900 $courseconfig = get_config('moodlecourse'); 901 if (completion_info::is_enabled_for_site()) { 902 if (!array_key_exists('enablecompletion', $course)) { 903 $course['enablecompletion'] = $courseconfig->enablecompletion; 904 } 905 } else { 906 $course['enablecompletion'] = 0; 907 } 908 909 $course['category'] = $course['categoryid']; 910 911 // Summary format. 912 $course['summaryformat'] = external_validate_format($course['summaryformat']); 913 914 if (!empty($course['courseformatoptions'])) { 915 foreach ($course['courseformatoptions'] as $option) { 916 $course[$option['name']] = $option['value']; 917 } 918 } 919 920 // Custom fields. 921 if (!empty($course['customfields'])) { 922 foreach ($course['customfields'] as $field) { 923 $course['customfield_'.$field['shortname']] = $field['value']; 924 } 925 } 926 927 //Note: create_course() core function check shortname, idnumber, category 928 $course['id'] = create_course((object) $course)->id; 929 930 $resultcourses[] = array('id' => $course['id'], 'shortname' => $course['shortname']); 931 } 932 933 $transaction->allow_commit(); 934 935 return $resultcourses; 936 } 937 938 /** 939 * Returns description of method result value 940 * 941 * @return external_description 942 * @since Moodle 2.2 943 */ 944 public static function create_courses_returns() { 945 return new external_multiple_structure( 946 new external_single_structure( 947 array( 948 'id' => new external_value(PARAM_INT, 'course id'), 949 'shortname' => new external_value(PARAM_RAW, 'short name'), 950 ) 951 ) 952 ); 953 } 954 955 /** 956 * Update courses 957 * 958 * @return external_function_parameters 959 * @since Moodle 2.5 960 */ 961 public static function update_courses_parameters() { 962 return new external_function_parameters( 963 array( 964 'courses' => new external_multiple_structure( 965 new external_single_structure( 966 array( 967 'id' => new external_value(PARAM_INT, 'ID of the course'), 968 'fullname' => new external_value(PARAM_TEXT, 'full name', VALUE_OPTIONAL), 969 'shortname' => new external_value(PARAM_TEXT, 'course short name', VALUE_OPTIONAL), 970 'categoryid' => new external_value(PARAM_INT, 'category id', VALUE_OPTIONAL), 971 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL), 972 'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL), 973 'summaryformat' => new external_format_value('summary', VALUE_OPTIONAL), 974 'format' => new external_value(PARAM_PLUGIN, 975 'course format: weeks, topics, social, site,..', VALUE_OPTIONAL), 976 'showgrades' => new external_value(PARAM_INT, 977 '1 if grades are shown, otherwise 0', VALUE_OPTIONAL), 978 'newsitems' => new external_value(PARAM_INT, 979 'number of recent items appearing on the course page', VALUE_OPTIONAL), 980 'startdate' => new external_value(PARAM_INT, 981 'timestamp when the course start', VALUE_OPTIONAL), 982 'enddate' => new external_value(PARAM_INT, 983 'timestamp when the course end', VALUE_OPTIONAL), 984 'numsections' => new external_value(PARAM_INT, 985 '(deprecated, use courseformatoptions) number of weeks/topics', VALUE_OPTIONAL), 986 'maxbytes' => new external_value(PARAM_INT, 987 'largest size of file that can be uploaded into the course', VALUE_OPTIONAL), 988 'showreports' => new external_value(PARAM_INT, 989 'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL), 990 'visible' => new external_value(PARAM_INT, 991 '1: available to student, 0:not available', VALUE_OPTIONAL), 992 'hiddensections' => new external_value(PARAM_INT, 993 '(deprecated, use courseformatoptions) How the hidden sections in the course are 994 displayed to students', VALUE_OPTIONAL), 995 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL), 996 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL), 997 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL), 998 'enablecompletion' => new external_value(PARAM_INT, 999 'Enabled, control via completion and activity settings. Disabled, 1000 not shown in activity settings.', VALUE_OPTIONAL), 1001 'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL), 1002 'lang' => new external_value(PARAM_SAFEDIR, 'forced course language', VALUE_OPTIONAL), 1003 'forcetheme' => new external_value(PARAM_PLUGIN, 'name of the force theme', VALUE_OPTIONAL), 1004 'courseformatoptions' => new external_multiple_structure( 1005 new external_single_structure( 1006 array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'), 1007 'value' => new external_value(PARAM_RAW, 'course format option value') 1008 )), 'additional options for particular course format', VALUE_OPTIONAL), 1009 'customfields' => new external_multiple_structure( 1010 new external_single_structure( 1011 [ 1012 'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'), 1013 'value' => new external_value(PARAM_RAW, 'The value of the custom field') 1014 ] 1015 ), 'Custom fields', VALUE_OPTIONAL), 1016 ) 1017 ), 'courses to update' 1018 ) 1019 ) 1020 ); 1021 } 1022 1023 /** 1024 * Update courses 1025 * 1026 * @param array $courses 1027 * @since Moodle 2.5 1028 */ 1029 public static function update_courses($courses) { 1030 global $CFG, $DB; 1031 require_once($CFG->dirroot . "/course/lib.php"); 1032 $warnings = array(); 1033 1034 $params = self::validate_parameters(self::update_courses_parameters(), 1035 array('courses' => $courses)); 1036 1037 $availablethemes = core_component::get_plugin_list('theme'); 1038 $availablelangs = get_string_manager()->get_list_of_translations(); 1039 1040 foreach ($params['courses'] as $course) { 1041 // Catch any exception while updating course and return as warning to user. 1042 try { 1043 // Ensure the current user is allowed to run this function. 1044 $context = context_course::instance($course['id'], MUST_EXIST); 1045 self::validate_context($context); 1046 1047 $oldcourse = course_get_format($course['id'])->get_course(); 1048 1049 require_capability('moodle/course:update', $context); 1050 1051 // Check if user can change category. 1052 if (array_key_exists('categoryid', $course) && ($oldcourse->category != $course['categoryid'])) { 1053 require_capability('moodle/course:changecategory', $context); 1054 $course['category'] = $course['categoryid']; 1055 } 1056 1057 // Check if the user can change fullname, and the new value is non-empty. 1058 if (array_key_exists('fullname', $course) && ($oldcourse->fullname != $course['fullname'])) { 1059 require_capability('moodle/course:changefullname', $context); 1060 if (trim($course['fullname']) === '') { 1061 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname'); 1062 } 1063 } 1064 1065 // Check if the user can change shortname, and the new value is non-empty. 1066 if (array_key_exists('shortname', $course) && ($oldcourse->shortname != $course['shortname'])) { 1067 require_capability('moodle/course:changeshortname', $context); 1068 if (trim($course['shortname']) === '') { 1069 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname'); 1070 } 1071 } 1072 1073 // Check if the user can change the idnumber. 1074 if (array_key_exists('idnumber', $course) && ($oldcourse->idnumber != $course['idnumber'])) { 1075 require_capability('moodle/course:changeidnumber', $context); 1076 } 1077 1078 // Check if user can change summary. 1079 if (array_key_exists('summary', $course) && ($oldcourse->summary != $course['summary'])) { 1080 require_capability('moodle/course:changesummary', $context); 1081 } 1082 1083 // Summary format. 1084 if (array_key_exists('summaryformat', $course) && ($oldcourse->summaryformat != $course['summaryformat'])) { 1085 require_capability('moodle/course:changesummary', $context); 1086 $course['summaryformat'] = external_validate_format($course['summaryformat']); 1087 } 1088 1089 // Check if user can change visibility. 1090 if (array_key_exists('visible', $course) && ($oldcourse->visible != $course['visible'])) { 1091 require_capability('moodle/course:visibility', $context); 1092 } 1093 1094 // Make sure lang is valid. 1095 if (array_key_exists('lang', $course) && ($oldcourse->lang != $course['lang'])) { 1096 require_capability('moodle/course:setforcedlanguage', $context); 1097 if (empty($availablelangs[$course['lang']])) { 1098 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang'); 1099 } 1100 } 1101 1102 // Make sure theme is valid. 1103 if (array_key_exists('forcetheme', $course)) { 1104 if (!empty($CFG->allowcoursethemes)) { 1105 if (empty($availablethemes[$course['forcetheme']])) { 1106 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme'); 1107 } else { 1108 $course['theme'] = $course['forcetheme']; 1109 } 1110 } 1111 } 1112 1113 // Make sure completion is enabled before setting it. 1114 if (array_key_exists('enabledcompletion', $course) && !completion_info::is_enabled_for_site()) { 1115 $course['enabledcompletion'] = 0; 1116 } 1117 1118 // Make sure maxbytes are less then CFG->maxbytes. 1119 if (array_key_exists('maxbytes', $course)) { 1120 // We allow updates back to 0 max bytes, a special value denoting the course uses the site limit. 1121 // Otherwise, either use the size specified, or cap at the max size for the course. 1122 if ($course['maxbytes'] != 0) { 1123 $course['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $course['maxbytes']); 1124 } 1125 } 1126 1127 if (!empty($course['courseformatoptions'])) { 1128 foreach ($course['courseformatoptions'] as $option) { 1129 if (isset($option['name']) && isset($option['value'])) { 1130 $course[$option['name']] = $option['value']; 1131 } 1132 } 1133 } 1134 1135 // Prepare list of custom fields. 1136 if (isset($course['customfields'])) { 1137 foreach ($course['customfields'] as $field) { 1138 $course['customfield_' . $field['shortname']] = $field['value']; 1139 } 1140 } 1141 1142 // Update course if user has all required capabilities. 1143 update_course((object) $course); 1144 } catch (Exception $e) { 1145 $warning = array(); 1146 $warning['item'] = 'course'; 1147 $warning['itemid'] = $course['id']; 1148 if ($e instanceof moodle_exception) { 1149 $warning['warningcode'] = $e->errorcode; 1150 } else { 1151 $warning['warningcode'] = $e->getCode(); 1152 } 1153 $warning['message'] = $e->getMessage(); 1154 $warnings[] = $warning; 1155 } 1156 } 1157 1158 $result = array(); 1159 $result['warnings'] = $warnings; 1160 return $result; 1161 } 1162 1163 /** 1164 * Returns description of method result value 1165 * 1166 * @return external_description 1167 * @since Moodle 2.5 1168 */ 1169 public static function update_courses_returns() { 1170 return new external_single_structure( 1171 array( 1172 'warnings' => new external_warnings() 1173 ) 1174 ); 1175 } 1176 1177 /** 1178 * Returns description of method parameters 1179 * 1180 * @return external_function_parameters 1181 * @since Moodle 2.2 1182 */ 1183 public static function delete_courses_parameters() { 1184 return new external_function_parameters( 1185 array( 1186 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID')), 1187 ) 1188 ); 1189 } 1190 1191 /** 1192 * Delete courses 1193 * 1194 * @param array $courseids A list of course ids 1195 * @since Moodle 2.2 1196 */ 1197 public static function delete_courses($courseids) { 1198 global $CFG, $DB; 1199 require_once($CFG->dirroot."/course/lib.php"); 1200 1201 // Parameter validation. 1202 $params = self::validate_parameters(self::delete_courses_parameters(), array('courseids'=>$courseids)); 1203 1204 $warnings = array(); 1205 1206 foreach ($params['courseids'] as $courseid) { 1207 $course = $DB->get_record('course', array('id' => $courseid)); 1208 1209 if ($course === false) { 1210 $warnings[] = array( 1211 'item' => 'course', 1212 'itemid' => $courseid, 1213 'warningcode' => 'unknowncourseidnumber', 1214 'message' => 'Unknown course ID ' . $courseid 1215 ); 1216 continue; 1217 } 1218 1219 // Check if the context is valid. 1220 $coursecontext = context_course::instance($course->id); 1221 self::validate_context($coursecontext); 1222 1223 // Check if the current user has permission. 1224 if (!can_delete_course($courseid)) { 1225 $warnings[] = array( 1226 'item' => 'course', 1227 'itemid' => $courseid, 1228 'warningcode' => 'cannotdeletecourse', 1229 'message' => 'You do not have the permission to delete this course' . $courseid 1230 ); 1231 continue; 1232 } 1233 1234 if (delete_course($course, false) === false) { 1235 $warnings[] = array( 1236 'item' => 'course', 1237 'itemid' => $courseid, 1238 'warningcode' => 'cannotdeletecategorycourse', 1239 'message' => 'Course ' . $courseid . ' failed to be deleted' 1240 ); 1241 continue; 1242 } 1243 } 1244 1245 fix_course_sortorder(); 1246 1247 return array('warnings' => $warnings); 1248 } 1249 1250 /** 1251 * Returns description of method result value 1252 * 1253 * @return external_description 1254 * @since Moodle 2.2 1255 */ 1256 public static function delete_courses_returns() { 1257 return new external_single_structure( 1258 array( 1259 'warnings' => new external_warnings() 1260 ) 1261 ); 1262 } 1263 1264 /** 1265 * Returns description of method parameters 1266 * 1267 * @return external_function_parameters 1268 * @since Moodle 2.3 1269 */ 1270 public static function duplicate_course_parameters() { 1271 return new external_function_parameters( 1272 array( 1273 'courseid' => new external_value(PARAM_INT, 'course to duplicate id'), 1274 'fullname' => new external_value(PARAM_TEXT, 'duplicated course full name'), 1275 'shortname' => new external_value(PARAM_TEXT, 'duplicated course short name'), 1276 'categoryid' => new external_value(PARAM_INT, 'duplicated course category parent'), 1277 'visible' => new external_value(PARAM_INT, 'duplicated course visible, default to yes', VALUE_DEFAULT, 1), 1278 'options' => new external_multiple_structure( 1279 new external_single_structure( 1280 array( 1281 'name' => new external_value(PARAM_ALPHAEXT, 'The backup option name: 1282 "activities" (int) Include course activites (default to 1 that is equal to yes), 1283 "blocks" (int) Include course blocks (default to 1 that is equal to yes), 1284 "filters" (int) Include course filters (default to 1 that is equal to yes), 1285 "users" (int) Include users (default to 0 that is equal to no), 1286 "enrolments" (int) Include enrolment methods (default to 1 - restore only with users), 1287 "role_assignments" (int) Include role assignments (default to 0 that is equal to no), 1288 "comments" (int) Include user comments (default to 0 that is equal to no), 1289 "userscompletion" (int) Include user course completion information (default to 0 that is equal to no), 1290 "logs" (int) Include course logs (default to 0 that is equal to no), 1291 "grade_histories" (int) Include histories (default to 0 that is equal to no)' 1292 ), 1293 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)' 1294 ) 1295 ) 1296 ), VALUE_DEFAULT, array() 1297 ), 1298 ) 1299 ); 1300 } 1301 1302 /** 1303 * Duplicate a course 1304 * 1305 * @param int $courseid 1306 * @param string $fullname Duplicated course fullname 1307 * @param string $shortname Duplicated course shortname 1308 * @param int $categoryid Duplicated course parent category id 1309 * @param int $visible Duplicated course availability 1310 * @param array $options List of backup options 1311 * @return array New course info 1312 * @since Moodle 2.3 1313 */ 1314 public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible = 1, $options = array()) { 1315 global $CFG, $USER, $DB; 1316 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); 1317 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); 1318 1319 // Parameter validation. 1320 $params = self::validate_parameters( 1321 self::duplicate_course_parameters(), 1322 array( 1323 'courseid' => $courseid, 1324 'fullname' => $fullname, 1325 'shortname' => $shortname, 1326 'categoryid' => $categoryid, 1327 'visible' => $visible, 1328 'options' => $options 1329 ) 1330 ); 1331 1332 // Context validation. 1333 1334 if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) { 1335 throw new moodle_exception('invalidcourseid', 'error'); 1336 } 1337 1338 // Category where duplicated course is going to be created. 1339 $categorycontext = context_coursecat::instance($params['categoryid']); 1340 self::validate_context($categorycontext); 1341 1342 // Course to be duplicated. 1343 $coursecontext = context_course::instance($course->id); 1344 self::validate_context($coursecontext); 1345 1346 $backupdefaults = array( 1347 'activities' => 1, 1348 'blocks' => 1, 1349 'filters' => 1, 1350 'users' => 0, 1351 'enrolments' => backup::ENROL_WITHUSERS, 1352 'role_assignments' => 0, 1353 'comments' => 0, 1354 'userscompletion' => 0, 1355 'logs' => 0, 1356 'grade_histories' => 0 1357 ); 1358 1359 $backupsettings = array(); 1360 // Check for backup and restore options. 1361 if (!empty($params['options'])) { 1362 foreach ($params['options'] as $option) { 1363 1364 // Strict check for a correct value (allways 1 or 0, true or false). 1365 $value = clean_param($option['value'], PARAM_INT); 1366 1367 if ($value !== 0 and $value !== 1) { 1368 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']); 1369 } 1370 1371 if (!isset($backupdefaults[$option['name']])) { 1372 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']); 1373 } 1374 1375 $backupsettings[$option['name']] = $value; 1376 } 1377 } 1378 1379 // Capability checking. 1380 1381 // The backup controller check for this currently, this may be redundant. 1382 require_capability('moodle/course:create', $categorycontext); 1383 require_capability('moodle/restore:restorecourse', $categorycontext); 1384 require_capability('moodle/backup:backupcourse', $coursecontext); 1385 1386 if (!empty($backupsettings['users'])) { 1387 require_capability('moodle/backup:userinfo', $coursecontext); 1388 require_capability('moodle/restore:userinfo', $categorycontext); 1389 } 1390 1391 // Check if the shortname is used. 1392 if ($foundcourses = $DB->get_records('course', array('shortname'=>$shortname))) { 1393 foreach ($foundcourses as $foundcourse) { 1394 $foundcoursenames[] = $foundcourse->fullname; 1395 } 1396 1397 $foundcoursenamestring = implode(',', $foundcoursenames); 1398 throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring); 1399 } 1400 1401 // Backup the course. 1402 1403 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, 1404 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id); 1405 1406 foreach ($backupsettings as $name => $value) { 1407 if ($setting = $bc->get_plan()->get_setting($name)) { 1408 $bc->get_plan()->get_setting($name)->set_value($value); 1409 } 1410 } 1411 1412 $backupid = $bc->get_backupid(); 1413 $backupbasepath = $bc->get_plan()->get_basepath(); 1414 1415 $bc->execute_plan(); 1416 $results = $bc->get_results(); 1417 $file = $results['backup_destination']; 1418 1419 $bc->destroy(); 1420 1421 // Restore the backup immediately. 1422 1423 // Check if we need to unzip the file because the backup temp dir does not contains backup files. 1424 if (!file_exists($backupbasepath . "/moodle_backup.xml")) { 1425 $file->extract_to_pathname(get_file_packer('application/vnd.moodle.backup'), $backupbasepath); 1426 } 1427 1428 // Create new course. 1429 $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']); 1430 1431 $rc = new restore_controller($backupid, $newcourseid, 1432 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE); 1433 1434 foreach ($backupsettings as $name => $value) { 1435 $setting = $rc->get_plan()->get_setting($name); 1436 if ($setting->get_status() == backup_setting::NOT_LOCKED) { 1437 $setting->set_value($value); 1438 } 1439 } 1440 1441 if (!$rc->execute_precheck()) { 1442 $precheckresults = $rc->get_precheck_results(); 1443 if (is_array($precheckresults) && !empty($precheckresults['errors'])) { 1444 if (empty($CFG->keeptempdirectoriesonbackup)) { 1445 fulldelete($backupbasepath); 1446 } 1447 1448 $errorinfo = ''; 1449 1450 foreach ($precheckresults['errors'] as $error) { 1451 $errorinfo .= $error; 1452 } 1453 1454 if (array_key_exists('warnings', $precheckresults)) { 1455 foreach ($precheckresults['warnings'] as $warning) { 1456 $errorinfo .= $warning; 1457 } 1458 } 1459 1460 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo); 1461 } 1462 } 1463 1464 $rc->execute_plan(); 1465 $rc->destroy(); 1466 1467 $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST); 1468 $course->fullname = $params['fullname']; 1469 $course->shortname = $params['shortname']; 1470 $course->visible = $params['visible']; 1471 1472 // Set shortname and fullname back. 1473 $DB->update_record('course', $course); 1474 1475 if (empty($CFG->keeptempdirectoriesonbackup)) { 1476 fulldelete($backupbasepath); 1477 } 1478 1479 // Delete the course backup file created by this WebService. Originally located in the course backups area. 1480 $file->delete(); 1481 1482 return array('id' => $course->id, 'shortname' => $course->shortname); 1483 } 1484 1485 /** 1486 * Returns description of method result value 1487 * 1488 * @return external_description 1489 * @since Moodle 2.3 1490 */ 1491 public static function duplicate_course_returns() { 1492 return new external_single_structure( 1493 array( 1494 'id' => new external_value(PARAM_INT, 'course id'), 1495 'shortname' => new external_value(PARAM_RAW, 'short name'), 1496 ) 1497 ); 1498 } 1499 1500 /** 1501 * Returns description of method parameters for import_course 1502 * 1503 * @return external_function_parameters 1504 * @since Moodle 2.4 1505 */ 1506 public static function import_course_parameters() { 1507 return new external_function_parameters( 1508 array( 1509 'importfrom' => new external_value(PARAM_INT, 'the id of the course we are importing from'), 1510 'importto' => new external_value(PARAM_INT, 'the id of the course we are importing to'), 1511 'deletecontent' => new external_value(PARAM_INT, 'whether to delete the course content where we are importing to (default to 0 = No)', VALUE_DEFAULT, 0), 1512 'options' => new external_multiple_structure( 1513 new external_single_structure( 1514 array( 1515 'name' => new external_value(PARAM_ALPHA, 'The backup option name: 1516 "activities" (int) Include course activites (default to 1 that is equal to yes), 1517 "blocks" (int) Include course blocks (default to 1 that is equal to yes), 1518 "filters" (int) Include course filters (default to 1 that is equal to yes)' 1519 ), 1520 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)' 1521 ) 1522 ) 1523 ), VALUE_DEFAULT, array() 1524 ), 1525 ) 1526 ); 1527 } 1528 1529 /** 1530 * Imports a course 1531 * 1532 * @param int $importfrom The id of the course we are importing from 1533 * @param int $importto The id of the course we are importing to 1534 * @param bool $deletecontent Whether to delete the course we are importing to content 1535 * @param array $options List of backup options 1536 * @return null 1537 * @since Moodle 2.4 1538 */ 1539 public static function import_course($importfrom, $importto, $deletecontent = 0, $options = array()) { 1540 global $CFG, $USER, $DB; 1541 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); 1542 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); 1543 1544 // Parameter validation. 1545 $params = self::validate_parameters( 1546 self::import_course_parameters(), 1547 array( 1548 'importfrom' => $importfrom, 1549 'importto' => $importto, 1550 'deletecontent' => $deletecontent, 1551 'options' => $options 1552 ) 1553 ); 1554 1555 if ($params['deletecontent'] !== 0 and $params['deletecontent'] !== 1) { 1556 throw new moodle_exception('invalidextparam', 'webservice', '', $params['deletecontent']); 1557 } 1558 1559 // Context validation. 1560 1561 if (! ($importfrom = $DB->get_record('course', array('id'=>$params['importfrom'])))) { 1562 throw new moodle_exception('invalidcourseid', 'error'); 1563 } 1564 1565 if (! ($importto = $DB->get_record('course', array('id'=>$params['importto'])))) { 1566 throw new moodle_exception('invalidcourseid', 'error'); 1567 } 1568 1569 $importfromcontext = context_course::instance($importfrom->id); 1570 self::validate_context($importfromcontext); 1571 1572 $importtocontext = context_course::instance($importto->id); 1573 self::validate_context($importtocontext); 1574 1575 $backupdefaults = array( 1576 'activities' => 1, 1577 'blocks' => 1, 1578 'filters' => 1 1579 ); 1580 1581 $backupsettings = array(); 1582 1583 // Check for backup and restore options. 1584 if (!empty($params['options'])) { 1585 foreach ($params['options'] as $option) { 1586 1587 // Strict check for a correct value (allways 1 or 0, true or false). 1588 $value = clean_param($option['value'], PARAM_INT); 1589 1590 if ($value !== 0 and $value !== 1) { 1591 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']); 1592 } 1593 1594 if (!isset($backupdefaults[$option['name']])) { 1595 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']); 1596 } 1597 1598 $backupsettings[$option['name']] = $value; 1599 } 1600 } 1601 1602 // Capability checking. 1603 1604 require_capability('moodle/backup:backuptargetimport', $importfromcontext); 1605 require_capability('moodle/restore:restoretargetimport', $importtocontext); 1606 1607 $bc = new backup_controller(backup::TYPE_1COURSE, $importfrom->id, backup::FORMAT_MOODLE, 1608 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); 1609 1610 foreach ($backupsettings as $name => $value) { 1611 $bc->get_plan()->get_setting($name)->set_value($value); 1612 } 1613 1614 $backupid = $bc->get_backupid(); 1615 $backupbasepath = $bc->get_plan()->get_basepath(); 1616 1617 $bc->execute_plan(); 1618 $bc->destroy(); 1619 1620 // Restore the backup immediately. 1621 1622 // Check if we must delete the contents of the destination course. 1623 if ($params['deletecontent']) { 1624 $restoretarget = backup::TARGET_EXISTING_DELETING; 1625 } else { 1626 $restoretarget = backup::TARGET_EXISTING_ADDING; 1627 } 1628 1629 $rc = new restore_controller($backupid, $importto->id, 1630 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, $restoretarget); 1631 1632 foreach ($backupsettings as $name => $value) { 1633 $rc->get_plan()->get_setting($name)->set_value($value); 1634 } 1635 1636 if (!$rc->execute_precheck()) { 1637 $precheckresults = $rc->get_precheck_results(); 1638 if (is_array($precheckresults) && !empty($precheckresults['errors'])) { 1639 if (empty($CFG->keeptempdirectoriesonbackup)) { 1640 fulldelete($backupbasepath); 1641 } 1642 1643 $errorinfo = ''; 1644 1645 foreach ($precheckresults['errors'] as $error) { 1646 $errorinfo .= $error; 1647 } 1648 1649 if (array_key_exists('warnings', $precheckresults)) { 1650 foreach ($precheckresults['warnings'] as $warning) { 1651 $errorinfo .= $warning; 1652 } 1653 } 1654 1655 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo); 1656 } 1657 } else { 1658 if ($restoretarget == backup::TARGET_EXISTING_DELETING) { 1659 restore_dbops::delete_course_content($importto->id); 1660 } 1661 } 1662 1663 $rc->execute_plan(); 1664 $rc->destroy(); 1665 1666 if (empty($CFG->keeptempdirectoriesonbackup)) { 1667 fulldelete($backupbasepath); 1668 } 1669 1670 return null; 1671 } 1672 1673 /** 1674 * Returns description of method result value 1675 * 1676 * @return external_description 1677 * @since Moodle 2.4 1678 */ 1679 public static function import_course_returns() { 1680 return null; 1681 } 1682 1683 /** 1684 * Returns description of method parameters 1685 * 1686 * @return external_function_parameters 1687 * @since Moodle 2.3 1688 */ 1689 public static function get_categories_parameters() { 1690 return new external_function_parameters( 1691 array( 1692 'criteria' => new external_multiple_structure( 1693 new external_single_structure( 1694 array( 1695 'key' => new external_value(PARAM_ALPHA, 1696 'The category column to search, expected keys (value format) are:'. 1697 '"id" (int) the category id,'. 1698 '"ids" (string) category ids separated by commas,'. 1699 '"name" (string) the category name,'. 1700 '"parent" (int) the parent category id,'. 1701 '"idnumber" (string) category idnumber'. 1702 ' - user must have \'moodle/category:manage\' to search on idnumber,'. 1703 '"visible" (int) whether the returned categories must be visible or hidden. If the key is not passed, 1704 then the function return all categories that the user can see.'. 1705 ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'. 1706 '"theme" (string) only return the categories having this theme'. 1707 ' - user must have \'moodle/category:manage\' to search on theme'), 1708 'value' => new external_value(PARAM_RAW, 'the value to match') 1709 ) 1710 ), 'criteria', VALUE_DEFAULT, array() 1711 ), 1712 'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos 1713 (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1) 1714 ) 1715 ); 1716 } 1717 1718 /** 1719 * Get categories 1720 * 1721 * @param array $criteria Criteria to match the results 1722 * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default) 1723 * @return array list of categories 1724 * @since Moodle 2.3 1725 */ 1726 public static function get_categories($criteria = array(), $addsubcategories = true) { 1727 global $CFG, $DB; 1728 require_once($CFG->dirroot . "/course/lib.php"); 1729 1730 // Validate parameters. 1731 $params = self::validate_parameters(self::get_categories_parameters(), 1732 array('criteria' => $criteria, 'addsubcategories' => $addsubcategories)); 1733 1734 // Retrieve the categories. 1735 $categories = array(); 1736 if (!empty($params['criteria'])) { 1737 1738 $conditions = array(); 1739 $wheres = array(); 1740 foreach ($params['criteria'] as $crit) { 1741 $key = trim($crit['key']); 1742 1743 // Trying to avoid duplicate keys. 1744 if (!isset($conditions[$key])) { 1745 1746 $context = context_system::instance(); 1747 $value = null; 1748 switch ($key) { 1749 case 'id': 1750 $value = clean_param($crit['value'], PARAM_INT); 1751 $conditions[$key] = $value; 1752 $wheres[] = $key . " = :" . $key; 1753 break; 1754 1755 case 'ids': 1756 $value = clean_param($crit['value'], PARAM_SEQUENCE); 1757 $ids = explode(',', $value); 1758 list($sqlids, $paramids) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED); 1759 $conditions = array_merge($conditions, $paramids); 1760 $wheres[] = 'id ' . $sqlids; 1761 break; 1762 1763 case 'idnumber': 1764 if (has_capability('moodle/category:manage', $context)) { 1765 $value = clean_param($crit['value'], PARAM_RAW); 1766 $conditions[$key] = $value; 1767 $wheres[] = $key . " = :" . $key; 1768 } else { 1769 // We must throw an exception. 1770 // Otherwise the dev client would think no idnumber exists. 1771 throw new moodle_exception('criteriaerror', 1772 'webservice', '', null, 1773 'You don\'t have the permissions to search on the "idnumber" field.'); 1774 } 1775 break; 1776 1777 case 'name': 1778 $value = clean_param($crit['value'], PARAM_TEXT); 1779 $conditions[$key] = $value; 1780 $wheres[] = $key . " = :" . $key; 1781 break; 1782 1783 case 'parent': 1784 $value = clean_param($crit['value'], PARAM_INT); 1785 $conditions[$key] = $value; 1786 $wheres[] = $key . " = :" . $key; 1787 break; 1788 1789 case 'visible': 1790 if (has_capability('moodle/category:viewhiddencategories', $context)) { 1791 $value = clean_param($crit['value'], PARAM_INT); 1792 $conditions[$key] = $value; 1793 $wheres[] = $key . " = :" . $key; 1794 } else { 1795 throw new moodle_exception('criteriaerror', 1796 'webservice', '', null, 1797 'You don\'t have the permissions to search on the "visible" field.'); 1798 } 1799 break; 1800 1801 case 'theme': 1802 if (has_capability('moodle/category:manage', $context)) { 1803 $value = clean_param($crit['value'], PARAM_THEME); 1804 $conditions[$key] = $value; 1805 $wheres[] = $key . " = :" . $key; 1806 } else { 1807 throw new moodle_exception('criteriaerror', 1808 'webservice', '', null, 1809 'You don\'t have the permissions to search on the "theme" field.'); 1810 } 1811 break; 1812 1813 default: 1814 throw new moodle_exception('criteriaerror', 1815 'webservice', '', null, 1816 'You can not search on this criteria: ' . $key); 1817 } 1818 } 1819 } 1820 1821 if (!empty($wheres)) { 1822 $wheres = implode(" AND ", $wheres); 1823 1824 $categories = $DB->get_records_select('course_categories', $wheres, $conditions); 1825 1826 // Retrieve its sub subcategories (all levels). 1827 if ($categories and !empty($params['addsubcategories'])) { 1828 $newcategories = array(); 1829 1830 // Check if we required visible/theme checks. 1831 $additionalselect = ''; 1832 $additionalparams = array(); 1833 if (isset($conditions['visible'])) { 1834 $additionalselect .= ' AND visible = :visible'; 1835 $additionalparams['visible'] = $conditions['visible']; 1836 } 1837 if (isset($conditions['theme'])) { 1838 $additionalselect .= ' AND theme= :theme'; 1839 $additionalparams['theme'] = $conditions['theme']; 1840 } 1841 1842 foreach ($categories as $category) { 1843 $sqlselect = $DB->sql_like('path', ':path') . $additionalselect; 1844 $sqlparams = array('path' => $category->path.'/%') + $additionalparams; // It will NOT include the specified category. 1845 $subcategories = $DB->get_records_select('course_categories', $sqlselect, $sqlparams); 1846 $newcategories = $newcategories + $subcategories; // Both arrays have integer as keys. 1847 } 1848 $categories = $categories + $newcategories; 1849 } 1850 } 1851 1852 } else { 1853 // Retrieve all categories in the database. 1854 $categories = $DB->get_records('course_categories'); 1855 } 1856 1857 // The not returned categories. key => category id, value => reason of exclusion. 1858 $excludedcats = array(); 1859 1860 // The returned categories. 1861 $categoriesinfo = array(); 1862 1863 // We need to sort the categories by path. 1864 // The parent cats need to be checked by the algo first. 1865 usort($categories, "core_course_external::compare_categories_by_path"); 1866 1867 foreach ($categories as $category) { 1868 1869 // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return). 1870 $parents = explode('/', $category->path); 1871 unset($parents[0]); // First key is always empty because path start with / => /1/2/4. 1872 foreach ($parents as $parentid) { 1873 // Note: when the parent exclusion was due to the context, 1874 // the sub category could still be returned. 1875 if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') { 1876 $excludedcats[$category->id] = 'parent'; 1877 } 1878 } 1879 1880 // Check the user can use the category context. 1881 $context = context_coursecat::instance($category->id); 1882 try { 1883 self::validate_context($context); 1884 } catch (Exception $e) { 1885 $excludedcats[$category->id] = 'context'; 1886 1887 // If it was the requested category then throw an exception. 1888 if (isset($params['categoryid']) && $category->id == $params['categoryid']) { 1889 $exceptionparam = new stdClass(); 1890 $exceptionparam->message = $e->getMessage(); 1891 $exceptionparam->catid = $category->id; 1892 throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam); 1893 } 1894 } 1895 1896 // Return the category information. 1897 if (!isset($excludedcats[$category->id])) { 1898 1899 // Final check to see if the category is visible to the user. 1900 if (core_course_category::can_view_category($category)) { 1901 1902 $categoryinfo = array(); 1903 $categoryinfo['id'] = $category->id; 1904 $categoryinfo['name'] = external_format_string($category->name, $context); 1905 list($categoryinfo['description'], $categoryinfo['descriptionformat']) = 1906 external_format_text($category->description, $category->descriptionformat, 1907 $context->id, 'coursecat', 'description', null); 1908 $categoryinfo['parent'] = $category->parent; 1909 $categoryinfo['sortorder'] = $category->sortorder; 1910 $categoryinfo['coursecount'] = $category->coursecount; 1911 $categoryinfo['depth'] = $category->depth; 1912 $categoryinfo['path'] = $category->path; 1913 1914 // Some fields only returned for admin. 1915 if (has_capability('moodle/category:manage', $context)) { 1916 $categoryinfo['idnumber'] = $category->idnumber; 1917 $categoryinfo['visible'] = $category->visible; 1918 $categoryinfo['visibleold'] = $category->visibleold; 1919 $categoryinfo['timemodified'] = $category->timemodified; 1920 $categoryinfo['theme'] = clean_param($category->theme, PARAM_THEME); 1921 } 1922 1923 $categoriesinfo[] = $categoryinfo; 1924 } else { 1925 $excludedcats[$category->id] = 'visibility'; 1926 } 1927 } 1928 } 1929 1930 // Sorting the resulting array so it looks a bit better for the client developer. 1931 usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder"); 1932 1933 return $categoriesinfo; 1934 } 1935 1936 /** 1937 * Sort categories array by path 1938 * private function: only used by get_categories 1939 * 1940 * @param array $category1 1941 * @param array $category2 1942 * @return int result of strcmp 1943 * @since Moodle 2.3 1944 */ 1945 private static function compare_categories_by_path($category1, $category2) { 1946 return strcmp($category1->path, $category2->path); 1947 } 1948 1949 /** 1950 * Sort categories array by sortorder 1951 * private function: only used by get_categories 1952 * 1953 * @param array $category1 1954 * @param array $category2 1955 * @return int result of strcmp 1956 * @since Moodle 2.3 1957 */ 1958 private static function compare_categories_by_sortorder($category1, $category2) { 1959 return strcmp($category1['sortorder'], $category2['sortorder']); 1960 } 1961 1962 /** 1963 * Returns description of method result value 1964 * 1965 * @return external_description 1966 * @since Moodle 2.3 1967 */ 1968 public static function get_categories_returns() { 1969 return new external_multiple_structure( 1970 new external_single_structure( 1971 array( 1972 'id' => new external_value(PARAM_INT, 'category id'), 1973 'name' => new external_value(PARAM_RAW, 'category name'), 1974 'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL), 1975 'description' => new external_value(PARAM_RAW, 'category description'), 1976 'descriptionformat' => new external_format_value('description'), 1977 'parent' => new external_value(PARAM_INT, 'parent category id'), 1978 'sortorder' => new external_value(PARAM_INT, 'category sorting order'), 1979 'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'), 1980 'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL), 1981 'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL), 1982 'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL), 1983 'depth' => new external_value(PARAM_INT, 'category depth'), 1984 'path' => new external_value(PARAM_TEXT, 'category path'), 1985 'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL), 1986 ), 'List of categories' 1987 ) 1988 ); 1989 } 1990 1991 /** 1992 * Returns description of method parameters 1993 * 1994 * @return external_function_parameters 1995 * @since Moodle 2.3 1996 */ 1997 public static function create_categories_parameters() { 1998 return new external_function_parameters( 1999 array( 2000 'categories' => new external_multiple_structure( 2001 new external_single_structure( 2002 array( 2003 'name' => new external_value(PARAM_TEXT, 'new category name'), 2004 'parent' => new external_value(PARAM_INT, 2005 'the parent category id inside which the new category will be created 2006 - set to 0 for a root category', 2007 VALUE_DEFAULT, 0), 2008 'idnumber' => new external_value(PARAM_RAW, 2009 'the new category idnumber', VALUE_OPTIONAL), 2010 'description' => new external_value(PARAM_RAW, 2011 'the new category description', VALUE_OPTIONAL), 2012 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT), 2013 'theme' => new external_value(PARAM_THEME, 2014 'the new category theme. This option must be enabled on moodle', 2015 VALUE_OPTIONAL), 2016 ) 2017 ) 2018 ) 2019 ) 2020 ); 2021 } 2022 2023 /** 2024 * Create categories 2025 * 2026 * @param array $categories - see create_categories_parameters() for the array structure 2027 * @return array - see create_categories_returns() for the array structure 2028 * @since Moodle 2.3 2029 */ 2030 public static function create_categories($categories) { 2031 global $DB; 2032 2033 $params = self::validate_parameters(self::create_categories_parameters(), 2034 array('categories' => $categories)); 2035 2036 $transaction = $DB->start_delegated_transaction(); 2037 2038 $createdcategories = array(); 2039 foreach ($params['categories'] as $category) { 2040 if ($category['parent']) { 2041 if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) { 2042 throw new moodle_exception('unknowcategory'); 2043 } 2044 $context = context_coursecat::instance($category['parent']); 2045 } else { 2046 $context = context_system::instance(); 2047 } 2048 self::validate_context($context); 2049 require_capability('moodle/category:manage', $context); 2050 2051 // this will validate format and throw an exception if there are errors 2052 external_validate_format($category['descriptionformat']); 2053 2054 $newcategory = core_course_category::create($category); 2055 $context = context_coursecat::instance($newcategory->id); 2056 2057 $createdcategories[] = array( 2058 'id' => $newcategory->id, 2059 'name' => external_format_string($newcategory->name, $context), 2060 ); 2061 } 2062 2063 $transaction->allow_commit(); 2064 2065 return $createdcategories; 2066 } 2067 2068 /** 2069 * Returns description of method parameters 2070 * 2071 * @return external_function_parameters 2072 * @since Moodle 2.3 2073 */ 2074 public static function create_categories_returns() { 2075 return new external_multiple_structure( 2076 new external_single_structure( 2077 array( 2078 'id' => new external_value(PARAM_INT, 'new category id'), 2079 'name' => new external_value(PARAM_RAW, 'new category name'), 2080 ) 2081 ) 2082 ); 2083 } 2084 2085 /** 2086 * Returns description of method parameters 2087 * 2088 * @return external_function_parameters 2089 * @since Moodle 2.3 2090 */ 2091 public static function update_categories_parameters() { 2092 return new external_function_parameters( 2093 array( 2094 'categories' => new external_multiple_structure( 2095 new external_single_structure( 2096 array( 2097 'id' => new external_value(PARAM_INT, 'course id'), 2098 'name' => new external_value(PARAM_TEXT, 'category name', VALUE_OPTIONAL), 2099 'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL), 2100 'parent' => new external_value(PARAM_INT, 'parent category id', VALUE_OPTIONAL), 2101 'description' => new external_value(PARAM_RAW, 'category description', VALUE_OPTIONAL), 2102 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT), 2103 'theme' => new external_value(PARAM_THEME, 2104 'the category theme. This option must be enabled on moodle', VALUE_OPTIONAL), 2105 ) 2106 ) 2107 ) 2108 ) 2109 ); 2110 } 2111 2112 /** 2113 * Update categories 2114 * 2115 * @param array $categories The list of categories to update 2116 * @return null 2117 * @since Moodle 2.3 2118 */ 2119 public static function update_categories($categories) { 2120 global $DB; 2121 2122 // Validate parameters. 2123 $params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories)); 2124 2125 $transaction = $DB->start_delegated_transaction(); 2126 2127 foreach ($params['categories'] as $cat) { 2128 $category = core_course_category::get($cat['id']); 2129 2130 $categorycontext = context_coursecat::instance($cat['id']); 2131 self::validate_context($categorycontext); 2132 require_capability('moodle/category:manage', $categorycontext); 2133 2134 // If the category parent is being changed, check for capability in the new parent category 2135 if (isset($cat['parent']) && ($cat['parent'] !== $category->parent)) { 2136 if ($cat['parent'] == 0) { 2137 // Creating a top level category requires capability in the system context 2138 $parentcontext = context_system::instance(); 2139 } else { 2140 // Category context 2141 $parentcontext = context_coursecat::instance($cat['parent']); 2142 } 2143 self::validate_context($parentcontext); 2144 require_capability('moodle/category:manage', $parentcontext); 2145 } 2146 2147 // this will throw an exception if descriptionformat is not valid 2148 external_validate_format($cat['descriptionformat']); 2149 2150 $category->update($cat); 2151 } 2152 2153 $transaction->allow_commit(); 2154 } 2155 2156 /** 2157 * Returns description of method result value 2158 * 2159 * @return external_description 2160 * @since Moodle 2.3 2161 */ 2162 public static function update_categories_returns() { 2163 return null; 2164 } 2165 2166 /** 2167 * Returns description of method parameters 2168 * 2169 * @return external_function_parameters 2170 * @since Moodle 2.3 2171 */ 2172 public static function delete_categories_parameters() { 2173 return new external_function_parameters( 2174 array( 2175 'categories' => new external_multiple_structure( 2176 new external_single_structure( 2177 array( 2178 'id' => new external_value(PARAM_INT, 'category id to delete'), 2179 'newparent' => new external_value(PARAM_INT, 2180 'the parent category to move the contents to, if specified', VALUE_OPTIONAL), 2181 'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this 2182 category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0) 2183 ) 2184 ) 2185 ) 2186 ) 2187 ); 2188 } 2189 2190 /** 2191 * Delete categories 2192 * 2193 * @param array $categories A list of category ids 2194 * @return array 2195 * @since Moodle 2.3 2196 */ 2197 public static function delete_categories($categories) { 2198 global $CFG, $DB; 2199 require_once($CFG->dirroot . "/course/lib.php"); 2200 2201 // Validate parameters. 2202 $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories)); 2203 2204 $transaction = $DB->start_delegated_transaction(); 2205 2206 foreach ($params['categories'] as $category) { 2207 $deletecat = core_course_category::get($category['id'], MUST_EXIST); 2208 $context = context_coursecat::instance($deletecat->id); 2209 require_capability('moodle/category:manage', $context); 2210 self::validate_context($context); 2211 self::validate_context(get_category_or_system_context($deletecat->parent)); 2212 2213 if ($category['recursive']) { 2214 // If recursive was specified, then we recursively delete the category's contents. 2215 if ($deletecat->can_delete_full()) { 2216 $deletecat->delete_full(false); 2217 } else { 2218 throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name()); 2219 } 2220 } else { 2221 // In this situation, we don't delete the category's contents, we either move it to newparent or parent. 2222 // If the parent is the root, moving is not supported (because a course must always be inside a category). 2223 // We must move to an existing category. 2224 if (!empty($category['newparent'])) { 2225 $newparentcat = core_course_category::get($category['newparent']); 2226 } else { 2227 $newparentcat = core_course_category::get($deletecat->parent); 2228 } 2229 2230 // This operation is not allowed. We must move contents to an existing category. 2231 if (!$newparentcat->id) { 2232 throw new moodle_exception('movecatcontentstoroot'); 2233 } 2234 2235 self::validate_context(context_coursecat::instance($newparentcat->id)); 2236 if ($deletecat->can_move_content_to($newparentcat->id)) { 2237 $deletecat->delete_move($newparentcat->id, false); 2238 } else { 2239 throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name()); 2240 } 2241 } 2242 } 2243 2244 $transaction->allow_commit(); 2245 } 2246 2247 /** 2248 * Returns description of method parameters 2249 * 2250 * @return external_function_parameters 2251 * @since Moodle 2.3 2252 */ 2253 public static function delete_categories_returns() { 2254 return null; 2255 } 2256 2257 /** 2258 * Describes the parameters for delete_modules. 2259 * 2260 * @return external_function_parameters 2261 * @since Moodle 2.5 2262 */ 2263 public static function delete_modules_parameters() { 2264 return new external_function_parameters ( 2265 array( 2266 'cmids' => new external_multiple_structure(new external_value(PARAM_INT, 'course module ID', 2267 VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of course module IDs'), 2268 ) 2269 ); 2270 } 2271 2272 /** 2273 * Deletes a list of provided module instances. 2274 * 2275 * @param array $cmids the course module ids 2276 * @since Moodle 2.5 2277 */ 2278 public static function delete_modules($cmids) { 2279 global $CFG, $DB; 2280 2281 // Require course file containing the course delete module function. 2282 require_once($CFG->dirroot . "/course/lib.php"); 2283 2284 // Clean the parameters. 2285 $params = self::validate_parameters(self::delete_modules_parameters(), array('cmids' => $cmids)); 2286 2287 // Keep track of the course ids we have performed a capability check on to avoid repeating. 2288 $arrcourseschecked = array(); 2289 2290 foreach ($params['cmids'] as $cmid) { 2291 // Get the course module. 2292 $cm = $DB->get_record('course_modules', array('id' => $cmid), '*', MUST_EXIST); 2293 2294 // Check if we have not yet confirmed they have permission in this course. 2295 if (!in_array($cm->course, $arrcourseschecked)) { 2296 // Ensure the current user has required permission in this course. 2297 $context = context_course::instance($cm->course); 2298 self::validate_context($context); 2299 // Add to the array. 2300 $arrcourseschecked[] = $cm->course; 2301 } 2302 2303 // Ensure they can delete this module. 2304 $modcontext = context_module::instance($cm->id); 2305 require_capability('moodle/course:manageactivities', $modcontext); 2306 2307 // Delete the module. 2308 course_delete_module($cm->id); 2309 } 2310 } 2311 2312 /** 2313 * Describes the delete_modules return value. 2314 * 2315 * @return external_single_structure 2316 * @since Moodle 2.5 2317 */ 2318 public static function delete_modules_returns() { 2319 return null; 2320 } 2321 2322 /** 2323 * Returns description of method parameters 2324 * 2325 * @return external_function_parameters 2326 * @since Moodle 2.9 2327 */ 2328 public static function view_course_parameters() { 2329 return new external_function_parameters( 2330 array( 2331 'courseid' => new external_value(PARAM_INT, 'id of the course'), 2332 'sectionnumber' => new external_value(PARAM_INT, 'section number', VALUE_DEFAULT, 0) 2333 ) 2334 ); 2335 } 2336 2337 /** 2338 * Trigger the course viewed event. 2339 * 2340 * @param int $courseid id of course 2341 * @param int $sectionnumber sectionnumber (0, 1, 2...) 2342 * @return array of warnings and status result 2343 * @since Moodle 2.9 2344 * @throws moodle_exception 2345 */ 2346 public static function view_course($courseid, $sectionnumber = 0) { 2347 global $CFG; 2348 require_once($CFG->dirroot . "/course/lib.php"); 2349 2350 $params = self::validate_parameters(self::view_course_parameters(), 2351 array( 2352 'courseid' => $courseid, 2353 'sectionnumber' => $sectionnumber 2354 )); 2355 2356 $warnings = array(); 2357 2358 $course = get_course($params['courseid']); 2359 $context = context_course::instance($course->id); 2360 self::validate_context($context); 2361 2362 if (!empty($params['sectionnumber'])) { 2363 2364 // Get section details and check it exists. 2365 $modinfo = get_fast_modinfo($course); 2366 $coursesection = $modinfo->get_section_info($params['sectionnumber'], MUST_EXIST); 2367 2368 // Check user is allowed to see it. 2369 if (!$coursesection->uservisible) { 2370 require_capability('moodle/course:viewhiddensections', $context); 2371 } 2372 } 2373 2374 course_view($context, $params['sectionnumber']); 2375 2376 $result = array(); 2377 $result['status'] = true; 2378 $result['warnings'] = $warnings; 2379 return $result; 2380 } 2381 2382 /** 2383 * Returns description of method result value 2384 * 2385 * @return external_description 2386 * @since Moodle 2.9 2387 */ 2388 public static function view_course_returns() { 2389 return new external_single_structure( 2390 array( 2391 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 2392 'warnings' => new external_warnings() 2393 ) 2394 ); 2395 } 2396 2397 /** 2398 * Returns description of method parameters 2399 * 2400 * @return external_function_parameters 2401 * @since Moodle 3.0 2402 */ 2403 public static function search_courses_parameters() { 2404 return new external_function_parameters( 2405 array( 2406 'criterianame' => new external_value(PARAM_ALPHA, 'criteria name 2407 (search, modulelist (only admins), blocklist (only admins), tagid)'), 2408 'criteriavalue' => new external_value(PARAM_RAW, 'criteria value'), 2409 'page' => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0), 2410 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0), 2411 'requiredcapabilities' => new external_multiple_structure( 2412 new external_value(PARAM_CAPABILITY, 'Capability string used to filter courses by permission'), 2413 'Optional list of required capabilities (used to filter the list)', VALUE_DEFAULT, array() 2414 ), 2415 'limittoenrolled' => new external_value(PARAM_BOOL, 'limit to enrolled courses', VALUE_DEFAULT, 0), 2416 'onlywithcompletion' => new external_value(PARAM_BOOL, 'limit to courses where completion is enabled', 2417 VALUE_DEFAULT, 0), 2418 ) 2419 ); 2420 } 2421 2422 /** 2423 * Return the course information that is public (visible by every one) 2424 * 2425 * @param core_course_list_element $course course in list object 2426 * @param stdClass $coursecontext course context object 2427 * @return array the course information 2428 * @since Moodle 3.2 2429 */ 2430 protected static function get_course_public_information(core_course_list_element $course, $coursecontext) { 2431 2432 static $categoriescache = array(); 2433 2434 // Category information. 2435 if (!array_key_exists($course->category, $categoriescache)) { 2436 $categoriescache[$course->category] = core_course_category::get($course->category, IGNORE_MISSING); 2437 } 2438 $category = $categoriescache[$course->category]; 2439 2440 // Retrieve course overview used files. 2441 $files = array(); 2442 foreach ($course->get_course_overviewfiles() as $file) { 2443 $fileurl = moodle_url::make_webservice_pluginfile_url($file->get_contextid(), $file->get_component(), 2444 $file->get_filearea(), null, $file->get_filepath(), 2445 $file->get_filename())->out(false); 2446 $files[] = array( 2447 'filename' => $file->get_filename(), 2448 'fileurl' => $fileurl, 2449 'filesize' => $file->get_filesize(), 2450 'filepath' => $file->get_filepath(), 2451 'mimetype' => $file->get_mimetype(), 2452 'timemodified' => $file->get_timemodified(), 2453 ); 2454 } 2455 2456 // Retrieve the course contacts, 2457 // we need here the users fullname since if we are not enrolled can be difficult to obtain them via other Web Services. 2458 $coursecontacts = array(); 2459 foreach ($course->get_course_contacts() as $contact) { 2460 $coursecontacts[] = array( 2461 'id' => $contact['user']->id, 2462 'fullname' => $contact['username'], 2463 'roles' => array_map(function($role){ 2464 return array('id' => $role->id, 'name' => $role->displayname); 2465 }, $contact['roles']), 2466 'role' => array('id' => $contact['role']->id, 'name' => $contact['role']->displayname), 2467 'rolename' => $contact['rolename'] 2468 ); 2469 } 2470 2471 // Allowed enrolment methods (maybe we can self-enrol). 2472 $enroltypes = array(); 2473 $instances = enrol_get_instances($course->id, true); 2474 foreach ($instances as $instance) { 2475 $enroltypes[] = $instance->enrol; 2476 } 2477 2478 // Format summary. 2479 list($summary, $summaryformat) = 2480 external_format_text($course->summary, $course->summaryformat, $coursecontext->id, 'course', 'summary', null); 2481 2482 $categoryname = ''; 2483 if (!empty($category)) { 2484 $categoryname = external_format_string($category->name, $category->get_context()); 2485 } 2486 2487 $displayname = get_course_display_name_for_list($course); 2488 $coursereturns = array(); 2489 $coursereturns['id'] = $course->id; 2490 $coursereturns['fullname'] = external_format_string($course->fullname, $coursecontext->id); 2491 $coursereturns['displayname'] = external_format_string($displayname, $coursecontext->id); 2492 $coursereturns['shortname'] = external_format_string($course->shortname, $coursecontext->id); 2493 $coursereturns['categoryid'] = $course->category; 2494 $coursereturns['categoryname'] = $categoryname; 2495 $coursereturns['summary'] = $summary; 2496 $coursereturns['summaryformat'] = $summaryformat; 2497 $coursereturns['summaryfiles'] = external_util::get_area_files($coursecontext->id, 'course', 'summary', false, false); 2498 $coursereturns['overviewfiles'] = $files; 2499 $coursereturns['contacts'] = $coursecontacts; 2500 $coursereturns['enrollmentmethods'] = $enroltypes; 2501 $coursereturns['sortorder'] = $course->sortorder; 2502 2503 $handler = core_course\customfield\course_handler::create(); 2504 if ($customfields = $handler->export_instance_data($course->id)) { 2505 $coursereturns['customfields'] = []; 2506 foreach ($customfields as $data) { 2507 $coursereturns['customfields'][] = [ 2508 'type' => $data->get_type(), 2509 'value' => $data->get_value(), 2510 'name' => $data->get_name(), 2511 'shortname' => $data->get_shortname() 2512 ]; 2513 } 2514 } 2515 2516 return $coursereturns; 2517 } 2518 2519 /** 2520 * Search courses following the specified criteria. 2521 * 2522 * @param string $criterianame Criteria name (search, modulelist (only admins), blocklist (only admins), tagid) 2523 * @param string $criteriavalue Criteria value 2524 * @param int $page Page number (for pagination) 2525 * @param int $perpage Items per page 2526 * @param array $requiredcapabilities Optional list of required capabilities (used to filter the list). 2527 * @param int $limittoenrolled Limit to only enrolled courses 2528 * @param int onlywithcompletion Limit to only courses where completion is enabled 2529 * @return array of course objects and warnings 2530 * @since Moodle 3.0 2531 * @throws moodle_exception 2532 */ 2533 public static function search_courses($criterianame, 2534 $criteriavalue, 2535 $page=0, 2536 $perpage=0, 2537 $requiredcapabilities=array(), 2538 $limittoenrolled=0, 2539 $onlywithcompletion=0) { 2540 global $CFG; 2541 2542 $warnings = array(); 2543 2544 $parameters = array( 2545 'criterianame' => $criterianame, 2546 'criteriavalue' => $criteriavalue, 2547 'page' => $page, 2548 'perpage' => $perpage, 2549 'requiredcapabilities' => $requiredcapabilities, 2550 'limittoenrolled' => $limittoenrolled, 2551 'onlywithcompletion' => $onlywithcompletion 2552 ); 2553 $params = self::validate_parameters(self::search_courses_parameters(), $parameters); 2554 self::validate_context(context_system::instance()); 2555 2556 $allowedcriterianames = array('search', 'modulelist', 'blocklist', 'tagid'); 2557 if (!in_array($params['criterianame'], $allowedcriterianames)) { 2558 throw new invalid_parameter_exception('Invalid value for criterianame parameter (value: '.$params['criterianame'].'),' . 2559 'allowed values are: '.implode(',', $allowedcriterianames)); 2560 } 2561 2562 if ($params['criterianame'] == 'modulelist' or $params['criterianame'] == 'blocklist') { 2563 require_capability('moodle/site:config', context_system::instance()); 2564 } 2565 2566 $paramtype = array( 2567 'search' => PARAM_RAW, 2568 'modulelist' => PARAM_PLUGIN, 2569 'blocklist' => PARAM_INT, 2570 'tagid' => PARAM_INT 2571 ); 2572 $params['criteriavalue'] = clean_param($params['criteriavalue'], $paramtype[$params['criterianame']]); 2573 2574 // Prepare the search API options. 2575 $searchcriteria = array(); 2576 $searchcriteria[$params['criterianame']] = $params['criteriavalue']; 2577 if ($params['onlywithcompletion']) { 2578 $searchcriteria['onlywithcompletion'] = true; 2579 } 2580 2581 $options = array(); 2582 if ($params['perpage'] != 0) { 2583 $offset = $params['page'] * $params['perpage']; 2584 $options = array('offset' => $offset, 'limit' => $params['perpage']); 2585 } 2586 2587 // Search the courses. 2588 $courses = core_course_category::search_courses($searchcriteria, $options, $params['requiredcapabilities']); 2589 $totalcount = core_course_category::search_courses_count($searchcriteria, $options, $params['requiredcapabilities']); 2590 2591 if (!empty($limittoenrolled)) { 2592 // Get the courses where the current user has access. 2593 $enrolled = enrol_get_my_courses(array('id', 'cacherev')); 2594 } 2595 2596 $finalcourses = array(); 2597 $categoriescache = array(); 2598 2599 foreach ($courses as $course) { 2600 if (!empty($limittoenrolled)) { 2601 // Filter out not enrolled courses. 2602 if (!isset($enrolled[$course->id])) { 2603 $totalcount--; 2604 continue; 2605 } 2606 } 2607 2608 $coursecontext = context_course::instance($course->id); 2609 2610 $finalcourses[] = self::get_course_public_information($course, $coursecontext); 2611 } 2612 2613 return array( 2614 'total' => $totalcount, 2615 'courses' => $finalcourses, 2616 'warnings' => $warnings 2617 ); 2618 } 2619 2620 /** 2621 * Returns a course structure definition 2622 * 2623 * @param boolean $onlypublicdata set to true, to retrieve only fields viewable by anyone when the course is visible 2624 * @return array the course structure 2625 * @since Moodle 3.2 2626 */ 2627 protected static function get_course_structure($onlypublicdata = true) { 2628 $coursestructure = array( 2629 'id' => new external_value(PARAM_INT, 'course id'), 2630 'fullname' => new external_value(PARAM_RAW, 'course full name'), 2631 'displayname' => new external_value(PARAM_RAW, 'course display name'), 2632 'shortname' => new external_value(PARAM_RAW, 'course short name'), 2633 'categoryid' => new external_value(PARAM_INT, 'category id'), 2634 'categoryname' => new external_value(PARAM_RAW, 'category name'), 2635 'sortorder' => new external_value(PARAM_INT, 'Sort order in the category', VALUE_OPTIONAL), 2636 'summary' => new external_value(PARAM_RAW, 'summary'), 2637 'summaryformat' => new external_format_value('summary'), 2638 'summaryfiles' => new external_files('summary files in the summary field', VALUE_OPTIONAL), 2639 'overviewfiles' => new external_files('additional overview files attached to this course'), 2640 'contacts' => new external_multiple_structure( 2641 new external_single_structure( 2642 array( 2643 'id' => new external_value(PARAM_INT, 'contact user id'), 2644 'fullname' => new external_value(PARAM_NOTAGS, 'contact user fullname'), 2645 ) 2646 ), 2647 'contact users' 2648 ), 2649 'enrollmentmethods' => new external_multiple_structure( 2650 new external_value(PARAM_PLUGIN, 'enrollment method'), 2651 'enrollment methods list' 2652 ), 2653 'customfields' => new external_multiple_structure( 2654 new external_single_structure( 2655 array( 2656 'name' => new external_value(PARAM_RAW, 'The name of the custom field'), 2657 'shortname' => new external_value(PARAM_RAW, 2658 'The shortname of the custom field - to be able to build the field class in the code'), 2659 'type' => new external_value(PARAM_ALPHANUMEXT, 2660 'The type of the custom field - text field, checkbox...'), 2661 'value' => new external_value(PARAM_RAW, 'The value of the custom field'), 2662 ) 2663 ), 'Custom fields', VALUE_OPTIONAL), 2664 ); 2665 2666 if (!$onlypublicdata) { 2667 $extra = array( 2668 'idnumber' => new external_value(PARAM_RAW, 'Id number', VALUE_OPTIONAL), 2669 'format' => new external_value(PARAM_PLUGIN, 'Course format: weeks, topics, social, site,..', VALUE_OPTIONAL), 2670 'showgrades' => new external_value(PARAM_INT, '1 if grades are shown, otherwise 0', VALUE_OPTIONAL), 2671 'newsitems' => new external_value(PARAM_INT, 'Number of recent items appearing on the course page', VALUE_OPTIONAL), 2672 'startdate' => new external_value(PARAM_INT, 'Timestamp when the course start', VALUE_OPTIONAL), 2673 'enddate' => new external_value(PARAM_INT, 'Timestamp when the course end', VALUE_OPTIONAL), 2674 'maxbytes' => new external_value(PARAM_INT, 'Largest size of file that can be uploaded into', VALUE_OPTIONAL), 2675 'showreports' => new external_value(PARAM_INT, 'Are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL), 2676 'visible' => new external_value(PARAM_INT, '1: available to student, 0:not available', VALUE_OPTIONAL), 2677 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL), 2678 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL), 2679 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL), 2680 'enablecompletion' => new external_value(PARAM_INT, 'Completion enabled? 1: yes 0: no', VALUE_OPTIONAL), 2681 'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL), 2682 'lang' => new external_value(PARAM_SAFEDIR, 'Forced course language', VALUE_OPTIONAL), 2683 'theme' => new external_value(PARAM_PLUGIN, 'Fame of the forced theme', VALUE_OPTIONAL), 2684 'marker' => new external_value(PARAM_INT, 'Current course marker', VALUE_OPTIONAL), 2685 'legacyfiles' => new external_value(PARAM_INT, 'If legacy files are enabled', VALUE_OPTIONAL), 2686 'calendartype' => new external_value(PARAM_PLUGIN, 'Calendar type', VALUE_OPTIONAL), 2687 'timecreated' => new external_value(PARAM_INT, 'Time when the course was created', VALUE_OPTIONAL), 2688 'timemodified' => new external_value(PARAM_INT, 'Last time the course was updated', VALUE_OPTIONAL), 2689 'requested' => new external_value(PARAM_INT, 'If is a requested course', VALUE_OPTIONAL), 2690 'cacherev' => new external_value(PARAM_INT, 'Cache revision number', VALUE_OPTIONAL), 2691 'filters' => new external_multiple_structure( 2692 new external_single_structure( 2693 array( 2694 'filter' => new external_value(PARAM_PLUGIN, 'Filter plugin name'), 2695 'localstate' => new external_value(PARAM_INT, 'Filter state: 1 for on, -1 for off, 0 if inherit'), 2696 'inheritedstate' => new external_value(PARAM_INT, '1 or 0 to use when localstate is set to inherit'), 2697 ) 2698 ), 2699 'Course filters', VALUE_OPTIONAL 2700 ), 2701 'courseformatoptions' => new external_multiple_structure( 2702 new external_single_structure( 2703 array( 2704 'name' => new external_value(PARAM_RAW, 'Course format option name.'), 2705 'value' => new external_value(PARAM_RAW, 'Course format option value.'), 2706 ) 2707 ), 2708 'Additional options for particular course format.', VALUE_OPTIONAL 2709 ), 2710 ); 2711 $coursestructure = array_merge($coursestructure, $extra); 2712 } 2713 return new external_single_structure($coursestructure); 2714 } 2715 2716 /** 2717 * Returns description of method result value 2718 * 2719 * @return external_description 2720 * @since Moodle 3.0 2721 */ 2722 public static function search_courses_returns() { 2723 return new external_single_structure( 2724 array( 2725 'total' => new external_value(PARAM_INT, 'total course count'), 2726 'courses' => new external_multiple_structure(self::get_course_structure(), 'course'), 2727 'warnings' => new external_warnings() 2728 ) 2729 ); 2730 } 2731 2732 /** 2733 * Returns description of method parameters 2734 * 2735 * @return external_function_parameters 2736 * @since Moodle 3.0 2737 */ 2738 public static function get_course_module_parameters() { 2739 return new external_function_parameters( 2740 array( 2741 'cmid' => new external_value(PARAM_INT, 'The course module id') 2742 ) 2743 ); 2744 } 2745 2746 /** 2747 * Return information about a course module. 2748 * 2749 * @param int $cmid the course module id 2750 * @return array of warnings and the course module 2751 * @since Moodle 3.0 2752 * @throws moodle_exception 2753 */ 2754 public static function get_course_module($cmid) { 2755 global $CFG, $DB; 2756 2757 $params = self::validate_parameters(self::get_course_module_parameters(), array('cmid' => $cmid)); 2758 $warnings = array(); 2759 2760 $cm = get_coursemodule_from_id(null, $params['cmid'], 0, true, MUST_EXIST); 2761 $context = context_module::instance($cm->id); 2762 self::validate_context($context); 2763 2764 // If the user has permissions to manage the activity, return all the information. 2765 if (has_capability('moodle/course:manageactivities', $context)) { 2766 require_once($CFG->dirroot . '/course/modlib.php'); 2767 require_once($CFG->libdir . '/gradelib.php'); 2768 2769 $info = $cm; 2770 // Get the extra information: grade, advanced grading and outcomes data. 2771 $course = get_course($cm->course); 2772 list($newcm, $newcontext, $module, $extrainfo, $cw) = get_moduleinfo_data($cm, $course); 2773 // Grades. 2774 $gradeinfo = array('grade', 'gradepass', 'gradecat'); 2775 foreach ($gradeinfo as $gfield) { 2776 if (isset($extrainfo->{$gfield})) { 2777 $info->{$gfield} = $extrainfo->{$gfield}; 2778 } 2779 } 2780 if (isset($extrainfo->grade) and $extrainfo->grade < 0) { 2781 $info->scale = $DB->get_field('scale', 'scale', array('id' => abs($extrainfo->grade))); 2782 } 2783 // Advanced grading. 2784 if (isset($extrainfo->_advancedgradingdata)) { 2785 $info->advancedgrading = array(); 2786 foreach ($extrainfo as $key => $val) { 2787 if (strpos($key, 'advancedgradingmethod_') === 0) { 2788 $info->advancedgrading[] = array( 2789 'area' => str_replace('advancedgradingmethod_', '', $key), 2790 'method' => $val 2791 ); 2792 } 2793 } 2794 } 2795 // Outcomes. 2796 foreach ($extrainfo as $key => $val) { 2797 if (strpos($key, 'outcome_') === 0) { 2798 if (!isset($info->outcomes)) { 2799 $info->outcomes = array(); 2800 } 2801 $id = str_replace('outcome_', '', $key); 2802 $outcome = grade_outcome::fetch(array('id' => $id)); 2803 $scaleitems = $outcome->load_scale(); 2804 $info->outcomes[] = array( 2805 'id' => $id, 2806 'name' => external_format_string($outcome->get_name(), $context->id), 2807 'scale' => $scaleitems->scale 2808 ); 2809 } 2810 } 2811 } else { 2812 // Return information is safe to show to any user. 2813 $info = new stdClass(); 2814 $info->id = $cm->id; 2815 $info->course = $cm->course; 2816 $info->module = $cm->module; 2817 $info->modname = $cm->modname; 2818 $info->instance = $cm->instance; 2819 $info->section = $cm->section; 2820 $info->sectionnum = $cm->sectionnum; 2821 $info->groupmode = $cm->groupmode; 2822 $info->groupingid = $cm->groupingid; 2823 $info->completion = $cm->completion; 2824 } 2825 // Format name. 2826 $info->name = external_format_string($cm->name, $context->id); 2827 $result = array(); 2828 $result['cm'] = $info; 2829 $result['warnings'] = $warnings; 2830 return $result; 2831 } 2832 2833 /** 2834 * Returns description of method result value 2835 * 2836 * @return external_description 2837 * @since Moodle 3.0 2838 */ 2839 public static function get_course_module_returns() { 2840 return new external_single_structure( 2841 array( 2842 'cm' => new external_single_structure( 2843 array( 2844 'id' => new external_value(PARAM_INT, 'The course module id'), 2845 'course' => new external_value(PARAM_INT, 'The course id'), 2846 'module' => new external_value(PARAM_INT, 'The module type id'), 2847 'name' => new external_value(PARAM_RAW, 'The activity name'), 2848 'modname' => new external_value(PARAM_COMPONENT, 'The module component name (forum, assign, etc..)'), 2849 'instance' => new external_value(PARAM_INT, 'The activity instance id'), 2850 'section' => new external_value(PARAM_INT, 'The module section id'), 2851 'sectionnum' => new external_value(PARAM_INT, 'The module section number'), 2852 'groupmode' => new external_value(PARAM_INT, 'Group mode'), 2853 'groupingid' => new external_value(PARAM_INT, 'Grouping id'), 2854 'completion' => new external_value(PARAM_INT, 'If completion is enabled'), 2855 'idnumber' => new external_value(PARAM_RAW, 'Module id number', VALUE_OPTIONAL), 2856 'added' => new external_value(PARAM_INT, 'Time added', VALUE_OPTIONAL), 2857 'score' => new external_value(PARAM_INT, 'Score', VALUE_OPTIONAL), 2858 'indent' => new external_value(PARAM_INT, 'Indentation', VALUE_OPTIONAL), 2859 'visible' => new external_value(PARAM_INT, 'If visible', VALUE_OPTIONAL), 2860 'visibleoncoursepage' => new external_value(PARAM_INT, 'If visible on course page', VALUE_OPTIONAL), 2861 'visibleold' => new external_value(PARAM_INT, 'Visible old', VALUE_OPTIONAL), 2862 'completiongradeitemnumber' => new external_value(PARAM_INT, 'Completion grade item', VALUE_OPTIONAL), 2863 'completionview' => new external_value(PARAM_INT, 'Completion view setting', VALUE_OPTIONAL), 2864 'completionexpected' => new external_value(PARAM_INT, 'Completion time expected', VALUE_OPTIONAL), 2865 'showdescription' => new external_value(PARAM_INT, 'If the description is showed', VALUE_OPTIONAL), 2866 'availability' => new external_value(PARAM_RAW, 'Availability settings', VALUE_OPTIONAL), 2867 'grade' => new external_value(PARAM_FLOAT, 'Grade (max value or scale id)', VALUE_OPTIONAL), 2868 'scale' => new external_value(PARAM_TEXT, 'Scale items (if used)', VALUE_OPTIONAL), 2869 'gradepass' => new external_value(PARAM_RAW, 'Grade to pass (float)', VALUE_OPTIONAL), 2870 'gradecat' => new external_value(PARAM_INT, 'Grade category', VALUE_OPTIONAL), 2871 'advancedgrading' => new external_multiple_structure( 2872 new external_single_structure( 2873 array( 2874 'area' => new external_value(PARAM_AREA, 'Gradable area name'), 2875 'method' => new external_value(PARAM_COMPONENT, 'Grading method'), 2876 ) 2877 ), 2878 'Advanced grading settings', VALUE_OPTIONAL 2879 ), 2880 'outcomes' => new external_multiple_structure( 2881 new external_single_structure( 2882 array( 2883 'id' => new external_value(PARAM_ALPHANUMEXT, 'Outcome id'), 2884 'name' => new external_value(PARAM_RAW, 'Outcome full name'), 2885 'scale' => new external_value(PARAM_TEXT, 'Scale items') 2886 ) 2887 ), 2888 'Outcomes information', VALUE_OPTIONAL 2889 ), 2890 ) 2891 ), 2892 'warnings' => new external_warnings() 2893 ) 2894 ); 2895 } 2896 2897 /** 2898 * Returns description of method parameters 2899 * 2900 * @return external_function_parameters 2901 * @since Moodle 3.0 2902 */ 2903 public static function get_course_module_by_instance_parameters() { 2904 return new external_function_parameters( 2905 array( 2906 'module' => new external_value(PARAM_COMPONENT, 'The module name'), 2907 'instance' => new external_value(PARAM_INT, 'The module instance id') 2908 ) 2909 ); 2910 } 2911 2912 /** 2913 * Return information about a course module. 2914 * 2915 * @param string $module the module name 2916 * @param int $instance the activity instance id 2917 * @return array of warnings and the course module 2918 * @since Moodle 3.0 2919 * @throws moodle_exception 2920 */ 2921 public static function get_course_module_by_instance($module, $instance) { 2922 2923 $params = self::validate_parameters(self::get_course_module_by_instance_parameters(), 2924 array( 2925 'module' => $module, 2926 'instance' => $instance, 2927 )); 2928 2929 $warnings = array(); 2930 $cm = get_coursemodule_from_instance($params['module'], $params['instance'], 0, false, MUST_EXIST); 2931 2932 return self::get_course_module($cm->id); 2933 } 2934 2935 /** 2936 * Returns description of method result value 2937 * 2938 * @return external_description 2939 * @since Moodle 3.0 2940 */ 2941 public static function get_course_module_by_instance_returns() { 2942 return self::get_course_module_returns(); 2943 } 2944 2945 /** 2946 * Returns description of method parameters 2947 * 2948 * @return external_function_parameters 2949 * @since Moodle 3.2 2950 */ 2951 public static function get_user_navigation_options_parameters() { 2952 return new external_function_parameters( 2953 array( 2954 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')), 2955 ) 2956 ); 2957 } 2958 2959 /** 2960 * Return a list of navigation options in a set of courses that are avaialable or not for the current user. 2961 * 2962 * @param array $courseids a list of course ids 2963 * @return array of warnings and the options availability 2964 * @since Moodle 3.2 2965 * @throws moodle_exception 2966 */ 2967 public static function get_user_navigation_options($courseids) { 2968 global $CFG; 2969 require_once($CFG->dirroot . '/course/lib.php'); 2970 2971 // Parameter validation. 2972 $params = self::validate_parameters(self::get_user_navigation_options_parameters(), array('courseids' => $courseids)); 2973 $courseoptions = array(); 2974 2975 list($courses, $warnings) = external_util::validate_courses($params['courseids'], array(), true); 2976 2977 if (!empty($courses)) { 2978 foreach ($courses as $course) { 2979 // Fix the context for the frontpage. 2980 if ($course->id == SITEID) { 2981 $course->context = context_system::instance(); 2982 } 2983 $navoptions = course_get_user_navigation_options($course->context, $course); 2984 $options = array(); 2985 foreach ($navoptions as $name => $available) { 2986 $options[] = array( 2987 'name' => $name, 2988 'available' => $available, 2989 ); 2990 } 2991 2992 $courseoptions[] = array( 2993 'id' => $course->id, 2994 'options' => $options 2995 ); 2996 } 2997 } 2998 2999 $result = array( 3000 'courses' => $courseoptions, 3001 'warnings' => $warnings 3002 ); 3003 return $result; 3004 } 3005 3006 /** 3007 * Returns description of method result value 3008 * 3009 * @return external_description 3010 * @since Moodle 3.2 3011 */ 3012 public static function get_user_navigation_options_returns() { 3013 return new external_single_structure( 3014 array( 3015 'courses' => new external_multiple_structure( 3016 new external_single_structure( 3017 array( 3018 'id' => new external_value(PARAM_INT, 'Course id'), 3019 'options' => new external_multiple_structure( 3020 new external_single_structure( 3021 array( 3022 'name' => new external_value(PARAM_ALPHANUMEXT, 'Option name'), 3023 'available' => new external_value(PARAM_BOOL, 'Whether the option is available or not'), 3024 ) 3025 ) 3026 ) 3027 ) 3028 ), 'List of courses' 3029 ), 3030 'warnings' => new external_warnings() 3031 ) 3032 ); 3033 } 3034 3035 /** 3036 * Returns description of method parameters 3037 * 3038 * @return external_function_parameters 3039 * @since Moodle 3.2 3040 */ 3041 public static function get_user_administration_options_parameters() { 3042 return new external_function_parameters( 3043 array( 3044 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')), 3045 ) 3046 ); 3047 } 3048 3049 /** 3050 * Return a list of administration options in a set of courses that are available or not for the current user. 3051 * 3052 * @param array $courseids a list of course ids 3053 * @return array of warnings and the options availability 3054 * @since Moodle 3.2 3055 * @throws moodle_exception 3056 */ 3057 public static function get_user_administration_options($courseids) { 3058 global $CFG; 3059 require_once($CFG->dirroot . '/course/lib.php'); 3060 3061 // Parameter validation. 3062 $params = self::validate_parameters(self::get_user_administration_options_parameters(), array('courseids' => $courseids)); 3063 $courseoptions = array(); 3064 3065 list($courses, $warnings) = external_util::validate_courses($params['courseids'], array(), true); 3066 3067 if (!empty($courses)) { 3068 foreach ($courses as $course) { 3069 $adminoptions = course_get_user_administration_options($course, $course->context); 3070 $options = array(); 3071 foreach ($adminoptions as $name => $available) { 3072 $options[] = array( 3073 'name' => $name, 3074 'available' => $available, 3075 ); 3076 } 3077 3078 $courseoptions[] = array( 3079 'id' => $course->id, 3080 'options' => $options 3081 ); 3082 } 3083 } 3084 3085 $result = array( 3086 'courses' => $courseoptions, 3087 'warnings' => $warnings 3088 ); 3089 return $result; 3090 } 3091 3092 /** 3093 * Returns description of method result value 3094 * 3095 * @return external_description 3096 * @since Moodle 3.2 3097 */ 3098 public static function get_user_administration_options_returns() { 3099 return self::get_user_navigation_options_returns(); 3100 } 3101 3102 /** 3103 * Returns description of method parameters 3104 * 3105 * @return external_function_parameters 3106 * @since Moodle 3.2 3107 */ 3108 public static function get_courses_by_field_parameters() { 3109 return new external_function_parameters( 3110 array( 3111 'field' => new external_value(PARAM_ALPHA, 'The field to search can be left empty for all courses or: 3112 id: course id 3113 ids: comma separated course ids 3114 shortname: course short name 3115 idnumber: course id number 3116 category: category id the course belongs to 3117 ', VALUE_DEFAULT, ''), 3118 'value' => new external_value(PARAM_RAW, 'The value to match', VALUE_DEFAULT, '') 3119 ) 3120 ); 3121 } 3122 3123 3124 /** 3125 * Get courses matching a specific field (id/s, shortname, idnumber, category) 3126 * 3127 * @param string $field field name to search, or empty for all courses 3128 * @param string $value value to search 3129 * @return array list of courses and warnings 3130 * @throws invalid_parameter_exception 3131 * @since Moodle 3.2 3132 */ 3133 public static function get_courses_by_field($field = '', $value = '') { 3134 global $DB, $CFG; 3135 require_once($CFG->dirroot . '/course/lib.php'); 3136 require_once($CFG->libdir . '/filterlib.php'); 3137 3138 $params = self::validate_parameters(self::get_courses_by_field_parameters(), 3139 array( 3140 'field' => $field, 3141 'value' => $value, 3142 ) 3143 ); 3144 $warnings = array(); 3145 3146 if (empty($params['field'])) { 3147 $courses = $DB->get_records('course', null, 'id ASC'); 3148 } else { 3149 switch ($params['field']) { 3150 case 'id': 3151 case 'category': 3152 $value = clean_param($params['value'], PARAM_INT); 3153 break; 3154 case 'ids': 3155 $value = clean_param($params['value'], PARAM_SEQUENCE); 3156 break; 3157 case 'shortname': 3158 $value = clean_param($params['value'], PARAM_TEXT); 3159 break; 3160 case 'idnumber': 3161 $value = clean_param($params['value'], PARAM_RAW); 3162 break; 3163 default: 3164 throw new invalid_parameter_exception('Invalid field name'); 3165 } 3166 3167 if ($params['field'] === 'ids') { 3168 // Preload categories to avoid loading one at a time. 3169 $courseids = explode(',', $value); 3170 list ($listsql, $listparams) = $DB->get_in_or_equal($courseids); 3171 $categoryids = $DB->get_fieldset_sql(" 3172 SELECT DISTINCT cc.id 3173 FROM {course} c 3174 JOIN {course_categories} cc ON cc.id = c.category 3175 WHERE c.id $listsql", $listparams); 3176 core_course_category::get_many($categoryids); 3177 3178 // Load and validate all courses. This is called because it loads the courses 3179 // more efficiently. 3180 list ($courses, $warnings) = external_util::validate_courses($courseids, [], 3181 false, true); 3182 } else { 3183 $courses = $DB->get_records('course', array($params['field'] => $value), 'id ASC'); 3184 } 3185 } 3186 3187 $coursesdata = array(); 3188 foreach ($courses as $course) { 3189 $context = context_course::instance($course->id); 3190 $canupdatecourse = has_capability('moodle/course:update', $context); 3191 $canviewhiddencourses = has_capability('moodle/course:viewhiddencourses', $context); 3192 3193 // Check if the course is visible in the site for the user. 3194 if (!$course->visible and !$canviewhiddencourses and !$canupdatecourse) { 3195 continue; 3196 } 3197 // Get the public course information, even if we are not enrolled. 3198 $courseinlist = new core_course_list_element($course); 3199 3200 // Now, check if we have access to the course, unless it was already checked. 3201 try { 3202 if (empty($course->contextvalidated)) { 3203 self::validate_context($context); 3204 } 3205 } catch (Exception $e) { 3206 // User can not access the course, check if they can see the public information about the course and return it. 3207 if (core_course_category::can_view_course_info($course)) { 3208 $coursesdata[$course->id] = self::get_course_public_information($courseinlist, $context); 3209 } 3210 continue; 3211 } 3212 $coursesdata[$course->id] = self::get_course_public_information($courseinlist, $context); 3213 // Return information for any user that can access the course. 3214 $coursefields = array('format', 'showgrades', 'newsitems', 'startdate', 'enddate', 'maxbytes', 'showreports', 'visible', 3215 'groupmode', 'groupmodeforce', 'defaultgroupingid', 'enablecompletion', 'completionnotify', 'lang', 'theme', 3216 'marker'); 3217 3218 // Course filters. 3219 $coursesdata[$course->id]['filters'] = filter_get_available_in_context($context); 3220 3221 // Information for managers only. 3222 if ($canupdatecourse) { 3223 $managerfields = array('idnumber', 'legacyfiles', 'calendartype', 'timecreated', 'timemodified', 'requested', 3224 'cacherev'); 3225 $coursefields = array_merge($coursefields, $managerfields); 3226 } 3227 3228 // Populate fields. 3229 foreach ($coursefields as $field) { 3230 $coursesdata[$course->id][$field] = $course->{$field}; 3231 } 3232 3233 // Clean lang and auth fields for external functions (it may content uninstalled themes or language packs). 3234 if (isset($coursesdata[$course->id]['theme'])) { 3235 $coursesdata[$course->id]['theme'] = clean_param($coursesdata[$course->id]['theme'], PARAM_THEME); 3236 } 3237 if (isset($coursesdata[$course->id]['lang'])) { 3238 $coursesdata[$course->id]['lang'] = clean_param($coursesdata[$course->id]['lang'], PARAM_LANG); 3239 } 3240 3241 $courseformatoptions = course_get_format($course)->get_config_for_external(); 3242 foreach ($courseformatoptions as $key => $value) { 3243 $coursesdata[$course->id]['courseformatoptions'][] = array( 3244 'name' => $key, 3245 'value' => $value 3246 ); 3247 } 3248 } 3249 3250 return array( 3251 'courses' => $coursesdata, 3252 'warnings' => $warnings 3253 ); 3254 } 3255 3256 /** 3257 * Returns description of method result value 3258 * 3259 * @return external_description 3260 * @since Moodle 3.2 3261 */ 3262 public static function get_courses_by_field_returns() { 3263 // Course structure, including not only public viewable fields. 3264 return new external_single_structure( 3265 array( 3266 'courses' => new external_multiple_structure(self::get_course_structure(false), 'Course'), 3267 'warnings' => new external_warnings() 3268 ) 3269 ); 3270 } 3271 3272 /** 3273 * Returns description of method parameters 3274 * 3275 * @return external_function_parameters 3276 * @since Moodle 3.2 3277 */ 3278 public static function check_updates_parameters() { 3279 return new external_function_parameters( 3280 array( 3281 'courseid' => new external_value(PARAM_INT, 'Course id to check'), 3282 'tocheck' => new external_multiple_structure( 3283 new external_single_structure( 3284 array( 3285 'contextlevel' => new external_value(PARAM_ALPHA, 'The context level for the file location. 3286 Only module supported right now.'), 3287 'id' => new external_value(PARAM_INT, 'Context instance id'), 3288 'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'), 3289 ) 3290 ), 3291 'Instances to check' 3292 ), 3293 'filter' => new external_multiple_structure( 3294 new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments, 3295 gradeitems, outcomes'), 3296 'Check only for updates in these areas', VALUE_DEFAULT, array() 3297 ) 3298 ) 3299 ); 3300 } 3301 3302 /** 3303 * Check if there is updates affecting the user for the given course and contexts. 3304 * Right now only modules are supported. 3305 * This WS calls mod_check_updates_since for each module to check if there is any update the user should we aware of. 3306 * 3307 * @param int $courseid the list of modules to check 3308 * @param array $tocheck the list of modules to check 3309 * @param array $filter check only for updates in these areas 3310 * @return array list of updates and warnings 3311 * @throws moodle_exception 3312 * @since Moodle 3.2 3313 */ 3314 public static function check_updates($courseid, $tocheck, $filter = array()) { 3315 global $CFG, $DB; 3316 require_once($CFG->dirroot . "/course/lib.php"); 3317 3318 $params = self::validate_parameters( 3319 self::check_updates_parameters(), 3320 array( 3321 'courseid' => $courseid, 3322 'tocheck' => $tocheck, 3323 'filter' => $filter, 3324 ) 3325 ); 3326 3327 $course = get_course($params['courseid']); 3328 $context = context_course::instance($course->id); 3329 self::validate_context($context); 3330 3331 list($instances, $warnings) = course_check_updates($course, $params['tocheck'], $filter); 3332 3333 $instancesformatted = array(); 3334 foreach ($instances as $instance) { 3335 $updates = array(); 3336 foreach ($instance['updates'] as $name => $data) { 3337 if (empty($data->updated)) { 3338 continue; 3339 } 3340 $updatedata = array( 3341 'name' => $name, 3342 ); 3343 if (!empty($data->timeupdated)) { 3344 $updatedata['timeupdated'] = $data->timeupdated; 3345 } 3346 if (!empty($data->itemids)) { 3347 $updatedata['itemids'] = $data->itemids; 3348 } 3349 $updates[] = $updatedata; 3350 } 3351 if (!empty($updates)) { 3352 $instancesformatted[] = array( 3353 'contextlevel' => $instance['contextlevel'], 3354 'id' => $instance['id'], 3355 'updates' => $updates 3356 ); 3357 } 3358 } 3359 3360 return array( 3361 'instances' => $instancesformatted, 3362 'warnings' => $warnings 3363 ); 3364 } 3365 3366 /** 3367 * Returns description of method result value 3368 * 3369 * @return external_description 3370 * @since Moodle 3.2 3371 */ 3372 public static function check_updates_returns() { 3373 return new external_single_structure( 3374 array( 3375 'instances' => new external_multiple_structure( 3376 new external_single_structure( 3377 array( 3378 'contextlevel' => new external_value(PARAM_ALPHA, 'The context level'), 3379 'id' => new external_value(PARAM_INT, 'Instance id'), 3380 'updates' => new external_multiple_structure( 3381 new external_single_structure( 3382 array( 3383 'name' => new external_value(PARAM_ALPHANUMEXT, 'Name of the area updated.'), 3384 'timeupdated' => new external_value(PARAM_INT, 'Last time was updated', VALUE_OPTIONAL), 3385 'itemids' => new external_multiple_structure( 3386 new external_value(PARAM_INT, 'Instance id'), 3387 'The ids of the items updated', 3388 VALUE_OPTIONAL 3389 ) 3390 ) 3391 ) 3392 ) 3393 ) 3394 ) 3395 ), 3396 'warnings' => new external_warnings() 3397 ) 3398 ); 3399 } 3400 3401 /** 3402 * Returns description of method parameters 3403 * 3404 * @return external_function_parameters 3405 * @since Moodle 3.3 3406 */ 3407 public static function get_updates_since_parameters() { 3408 return new external_function_parameters( 3409 array( 3410 'courseid' => new external_value(PARAM_INT, 'Course id to check'), 3411 'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'), 3412 'filter' => new external_multiple_structure( 3413 new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments, 3414 gradeitems, outcomes'), 3415 'Check only for updates in these areas', VALUE_DEFAULT, array() 3416 ) 3417 ) 3418 ); 3419 } 3420 3421 /** 3422 * Check if there are updates affecting the user for the given course since the given time stamp. 3423 * 3424 * This function is a wrapper of self::check_updates for retrieving all the updates since a given time for all the activities. 3425 * 3426 * @param int $courseid the list of modules to check 3427 * @param int $since check updates since this time stamp 3428 * @param array $filter check only for updates in these areas 3429 * @return array list of updates and warnings 3430 * @throws moodle_exception 3431 * @since Moodle 3.3 3432 */ 3433 public static function get_updates_since($courseid, $since, $filter = array()) { 3434 global $CFG, $DB; 3435 3436 $params = self::validate_parameters( 3437 self::get_updates_since_parameters(), 3438 array( 3439 'courseid' => $courseid, 3440 'since' => $since, 3441 'filter' => $filter, 3442 ) 3443 ); 3444 3445 $course = get_course($params['courseid']); 3446 $modinfo = get_fast_modinfo($course); 3447 $tocheck = array(); 3448 3449 // Retrieve all the visible course modules for the current user. 3450 $cms = $modinfo->get_cms(); 3451 foreach ($cms as $cm) { 3452 if (!$cm->uservisible) { 3453 continue; 3454 } 3455 $tocheck[] = array( 3456 'id' => $cm->id, 3457 'contextlevel' => 'module', 3458 'since' => $params['since'], 3459 ); 3460 } 3461 3462 return self::check_updates($course->id, $tocheck, $params['filter']); 3463 } 3464 3465 /** 3466 * Returns description of method result value 3467 * 3468 * @return external_description 3469 * @since Moodle 3.3 3470 */ 3471 public static function get_updates_since_returns() { 3472 return self::check_updates_returns(); 3473 } 3474 3475 /** 3476 * Parameters for function edit_module() 3477 * 3478 * @since Moodle 3.3 3479 * @return external_function_parameters 3480 */ 3481 public static function edit_module_parameters() { 3482 return new external_function_parameters( 3483 array( 3484 'action' => new external_value(PARAM_ALPHA, 3485 'action: hide, show, stealth, duplicate, delete, moveleft, moveright, group...', VALUE_REQUIRED), 3486 'id' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED), 3487 'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null), 3488 )); 3489 } 3490 3491 /** 3492 * Performs one of the edit module actions and return new html for AJAX 3493 * 3494 * Returns html to replace the current module html with, for example: 3495 * - empty string for "delete" action, 3496 * - two modules html for "duplicate" action 3497 * - updated module html for everything else 3498 * 3499 * Throws exception if operation is not permitted/possible 3500 * 3501 * @since Moodle 3.3 3502 * @param string $action 3503 * @param int $id 3504 * @param null|int $sectionreturn 3505 * @return string 3506 */ 3507 public static function edit_module($action, $id, $sectionreturn = null) { 3508 global $PAGE, $DB; 3509 // Validate and normalize parameters. 3510 $params = self::validate_parameters(self::edit_module_parameters(), 3511 array('action' => $action, 'id' => $id, 'sectionreturn' => $sectionreturn)); 3512 $action = $params['action']; 3513 $id = $params['id']; 3514 $sectionreturn = $params['sectionreturn']; 3515 3516 // Set of permissions an editing user may have. 3517 $contextarray = [ 3518 'moodle/course:update', 3519 'moodle/course:manageactivities', 3520 'moodle/course:activityvisibility', 3521 'moodle/course:sectionvisibility', 3522 'moodle/course:movesections', 3523 'moodle/course:setcurrentsection', 3524 ]; 3525 $PAGE->set_other_editing_capability($contextarray); 3526 3527 list($course, $cm) = get_course_and_cm_from_cmid($id); 3528 $modcontext = context_module::instance($cm->id); 3529 $coursecontext = context_course::instance($course->id); 3530 self::validate_context($modcontext); 3531 $courserenderer = $PAGE->get_renderer('core', 'course'); 3532 $completioninfo = new completion_info($course); 3533 3534 switch($action) { 3535 case 'hide': 3536 case 'show': 3537 case 'stealth': 3538 require_capability('moodle/course:activityvisibility', $modcontext); 3539 $visible = ($action === 'hide') ? 0 : 1; 3540 $visibleoncoursepage = ($action === 'stealth') ? 0 : 1; 3541 set_coursemodule_visible($id, $visible, $visibleoncoursepage); 3542 \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger(); 3543 break; 3544 case 'duplicate': 3545 require_capability('moodle/course:manageactivities', $coursecontext); 3546 require_capability('moodle/backup:backuptargetimport', $coursecontext); 3547 require_capability('moodle/restore:restoretargetimport', $coursecontext); 3548 if (!course_allowed_module($course, $cm->modname)) { 3549 throw new moodle_exception('No permission to create that activity'); 3550 } 3551 if ($newcm = duplicate_module($course, $cm)) { 3552 $cm = get_fast_modinfo($course)->get_cm($id); 3553 $newcm = get_fast_modinfo($course)->get_cm($newcm->id); 3554 return $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sectionreturn) . 3555 $courserenderer->course_section_cm_list_item($course, $completioninfo, $newcm, $sectionreturn); 3556 } 3557 break; 3558 case 'groupsseparate': 3559 case 'groupsvisible': 3560 case 'groupsnone': 3561 require_capability('moodle/course:manageactivities', $modcontext); 3562 if ($action === 'groupsseparate') { 3563 $newgroupmode = SEPARATEGROUPS; 3564 } else if ($action === 'groupsvisible') { 3565 $newgroupmode = VISIBLEGROUPS; 3566 } else { 3567 $newgroupmode = NOGROUPS; 3568 } 3569 if (set_coursemodule_groupmode($cm->id, $newgroupmode)) { 3570 \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger(); 3571 } 3572 break; 3573 case 'moveleft': 3574 case 'moveright': 3575 require_capability('moodle/course:manageactivities', $modcontext); 3576 $indent = $cm->indent + (($action === 'moveright') ? 1 : -1); 3577 if ($cm->indent >= 0) { 3578 $DB->update_record('course_modules', array('id' => $cm->id, 'indent' => $indent)); 3579 rebuild_course_cache($cm->course); 3580 } 3581 break; 3582 case 'delete': 3583 require_capability('moodle/course:manageactivities', $modcontext); 3584 course_delete_module($cm->id, true); 3585 return ''; 3586 default: 3587 throw new coding_exception('Unrecognised action'); 3588 } 3589 3590 $cm = get_fast_modinfo($course)->get_cm($id); 3591 return $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sectionreturn); 3592 } 3593 3594 /** 3595 * Return structure for edit_module() 3596 * 3597 * @since Moodle 3.3 3598 * @return external_description 3599 */ 3600 public static function edit_module_returns() { 3601 return new external_value(PARAM_RAW, 'html to replace the current module with'); 3602 } 3603 3604 /** 3605 * Parameters for function get_module() 3606 * 3607 * @since Moodle 3.3 3608 * @return external_function_parameters 3609 */ 3610 public static function get_module_parameters() { 3611 return new external_function_parameters( 3612 array( 3613 'id' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED), 3614 'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null), 3615 )); 3616 } 3617 3618 /** 3619 * Returns html for displaying one activity module on course page 3620 * 3621 * @since Moodle 3.3 3622 * @param int $id 3623 * @param null|int $sectionreturn 3624 * @return string 3625 */ 3626 public static function get_module($id, $sectionreturn = null) { 3627 global $PAGE; 3628 // Validate and normalize parameters. 3629 $params = self::validate_parameters(self::get_module_parameters(), 3630 array('id' => $id, 'sectionreturn' => $sectionreturn)); 3631 $id = $params['id']; 3632 $sectionreturn = $params['sectionreturn']; 3633 3634 // Set of permissions an editing user may have. 3635 $contextarray = [ 3636 'moodle/course:update', 3637 'moodle/course:manageactivities', 3638 'moodle/course:activityvisibility', 3639 'moodle/course:sectionvisibility', 3640 'moodle/course:movesections', 3641 'moodle/course:setcurrentsection', 3642 ]; 3643 $PAGE->set_other_editing_capability($contextarray); 3644 3645 // Validate access to the course (note, this is html for the course view page, we don't validate access to the module). 3646 list($course, $cm) = get_course_and_cm_from_cmid($id); 3647 self::validate_context(context_course::instance($course->id)); 3648 3649 $courserenderer = $PAGE->get_renderer('core', 'course'); 3650 $completioninfo = new completion_info($course); 3651 return $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sectionreturn); 3652 } 3653 3654 /** 3655 * Return structure for get_module() 3656 * 3657 * @since Moodle 3.3 3658 * @return external_description 3659 */ 3660 public static function get_module_returns() { 3661 return new external_value(PARAM_RAW, 'html to replace the current module with'); 3662 } 3663 3664 /** 3665 * Parameters for function edit_section() 3666 * 3667 * @since Moodle 3.3 3668 * @return external_function_parameters 3669 */ 3670 public static function edit_section_parameters() { 3671 return new external_function_parameters( 3672 array( 3673 'action' => new external_value(PARAM_ALPHA, 'action: hide, show, stealth, setmarker, removemarker', VALUE_REQUIRED), 3674 'id' => new external_value(PARAM_INT, 'course section id', VALUE_REQUIRED), 3675 'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null), 3676 )); 3677 } 3678 3679 /** 3680 * Performs one of the edit section actions 3681 * 3682 * @since Moodle 3.3 3683 * @param string $action 3684 * @param int $id section id 3685 * @param int $sectionreturn section to return to 3686 * @return string 3687 */ 3688 public static function edit_section($action, $id, $sectionreturn) { 3689 global $DB; 3690 // Validate and normalize parameters. 3691 $params = self::validate_parameters(self::edit_section_parameters(), 3692 array('action' => $action, 'id' => $id, 'sectionreturn' => $sectionreturn)); 3693 $action = $params['action']; 3694 $id = $params['id']; 3695 $sr = $params['sectionreturn']; 3696 3697 $section = $DB->get_record('course_sections', array('id' => $id), '*', MUST_EXIST); 3698 $coursecontext = context_course::instance($section->course); 3699 self::validate_context($coursecontext); 3700 3701 $rv = course_get_format($section->course)->section_action($section, $action, $sectionreturn); 3702 if ($rv) { 3703 return json_encode($rv); 3704 } else { 3705 return null; 3706 } 3707 } 3708 3709 /** 3710 * Return structure for edit_section() 3711 * 3712 * @since Moodle 3.3 3713 * @return external_description 3714 */ 3715 public static function edit_section_returns() { 3716 return new external_value(PARAM_RAW, 'Additional data for javascript (JSON-encoded string)'); 3717 } 3718 3719 /** 3720 * Returns description of method parameters 3721 * 3722 * @return external_function_parameters 3723 */ 3724 public static function get_enrolled_courses_by_timeline_classification_parameters() { 3725 return new external_function_parameters( 3726 array( 3727 'classification' => new external_value(PARAM_ALPHA, 'future, inprogress, or past'), 3728 'limit' => new external_value(PARAM_INT, 'Result set limit', VALUE_DEFAULT, 0), 3729 'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0), 3730 'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null), 3731 'customfieldname' => new external_value(PARAM_ALPHANUMEXT, 'Used when classification = customfield', 3732 VALUE_DEFAULT, null), 3733 'customfieldvalue' => new external_value(PARAM_RAW, 'Used when classification = customfield', 3734 VALUE_DEFAULT, null), 3735 ) 3736 ); 3737 } 3738 3739 /** 3740 * Get courses matching the given timeline classification. 3741 * 3742 * NOTE: The offset applies to the unfiltered full set of courses before the classification 3743 * filtering is done. 3744 * E.g. 3745 * If the user is enrolled in 5 courses: 3746 * c1, c2, c3, c4, and c5 3747 * And c4 and c5 are 'future' courses 3748 * 3749 * If a request comes in for future courses with an offset of 1 it will mean that 3750 * c1 is skipped (because the offset applies *before* the classification filtering) 3751 * and c4 and c5 will be return. 3752 * 3753 * @param string $classification past, inprogress, or future 3754 * @param int $limit Result set limit 3755 * @param int $offset Offset the full course set before timeline classification is applied 3756 * @param string $sort SQL sort string for results 3757 * @param string $customfieldname 3758 * @param string $customfieldvalue 3759 * @return array list of courses and warnings 3760 * @throws invalid_parameter_exception 3761 */ 3762 public static function get_enrolled_courses_by_timeline_classification( 3763 string $classification, 3764 int $limit = 0, 3765 int $offset = 0, 3766 string $sort = null, 3767 string $customfieldname = null, 3768 string $customfieldvalue = null 3769 ) { 3770 global $CFG, $PAGE, $USER; 3771 require_once($CFG->dirroot . '/course/lib.php'); 3772 3773 $params = self::validate_parameters(self::get_enrolled_courses_by_timeline_classification_parameters(), 3774 array( 3775 'classification' => $classification, 3776 'limit' => $limit, 3777 'offset' => $offset, 3778 'sort' => $sort, 3779 'customfieldvalue' => $customfieldvalue, 3780 ) 3781 ); 3782 3783 $classification = $params['classification']; 3784 $limit = $params['limit']; 3785 $offset = $params['offset']; 3786 $sort = $params['sort']; 3787 $customfieldvalue = $params['customfieldvalue']; 3788 3789 switch($classification) { 3790 case COURSE_TIMELINE_ALLINCLUDINGHIDDEN: 3791 break; 3792 case COURSE_TIMELINE_ALL: 3793 break; 3794 case COURSE_TIMELINE_PAST: 3795 break; 3796 case COURSE_TIMELINE_INPROGRESS: 3797 break; 3798 case COURSE_TIMELINE_FUTURE: 3799 break; 3800 case COURSE_FAVOURITES: 3801 break; 3802 case COURSE_TIMELINE_HIDDEN: 3803 break; 3804 case COURSE_CUSTOMFIELD: 3805 break; 3806 default: 3807 throw new invalid_parameter_exception('Invalid classification'); 3808 } 3809 3810 self::validate_context(context_user::instance($USER->id)); 3811 3812 $requiredproperties = course_summary_exporter::define_properties(); 3813 $fields = join(',', array_keys($requiredproperties)); 3814 $hiddencourses = get_hidden_courses_on_timeline(); 3815 $courses = []; 3816 3817 // If the timeline requires really all courses, get really all courses. 3818 if ($classification == COURSE_TIMELINE_ALLINCLUDINGHIDDEN) { 3819 $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields, COURSE_DB_QUERY_LIMIT); 3820 3821 // Otherwise if the timeline requires the hidden courses then restrict the result to only $hiddencourses. 3822 } else if ($classification == COURSE_TIMELINE_HIDDEN) { 3823 $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields, 3824 COURSE_DB_QUERY_LIMIT, $hiddencourses); 3825 3826 // Otherwise get the requested courses and exclude the hidden courses. 3827 } else { 3828 $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields, 3829 COURSE_DB_QUERY_LIMIT, [], $hiddencourses); 3830 } 3831 3832 $favouritecourseids = []; 3833 $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($USER->id)); 3834 $favourites = $ufservice->find_favourites_by_type('core_course', 'courses'); 3835 3836 if ($favourites) { 3837 $favouritecourseids = array_map( 3838 function($favourite) { 3839 return $favourite->itemid; 3840 }, $favourites); 3841 } 3842 3843 if ($classification == COURSE_FAVOURITES) { 3844 list($filteredcourses, $processedcount) = course_filter_courses_by_favourites( 3845 $courses, 3846 $favouritecourseids, 3847 $limit 3848 ); 3849 } else if ($classification == COURSE_CUSTOMFIELD) { 3850 list($filteredcourses, $processedcount) = course_filter_courses_by_customfield( 3851 $courses, 3852 $customfieldname, 3853 $customfieldvalue, 3854 $limit 3855 ); 3856 } else { 3857 list($filteredcourses, $processedcount) = course_filter_courses_by_timeline_classification( 3858 $courses, 3859 $classification, 3860 $limit 3861 ); 3862 } 3863 3864 $renderer = $PAGE->get_renderer('core'); 3865 $formattedcourses = array_map(function($course) use ($renderer, $favouritecourseids) { 3866 context_helper::preload_from_record($course); 3867 $context = context_course::instance($course->id); 3868 $isfavourite = false; 3869 if (in_array($course->id, $favouritecourseids)) { 3870 $isfavourite = true; 3871 } 3872 $exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => $isfavourite]); 3873 return $exporter->export($renderer); 3874 }, $filteredcourses); 3875 3876 return [ 3877 'courses' => $formattedcourses, 3878 'nextoffset' => $offset + $processedcount 3879 ]; 3880 } 3881 3882 /** 3883 * Returns description of method result value 3884 * 3885 * @return external_description 3886 */ 3887 public static function get_enrolled_courses_by_timeline_classification_returns() { 3888 return new external_single_structure( 3889 array( 3890 'courses' => new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Course'), 3891 'nextoffset' => new external_value(PARAM_INT, 'Offset for the next request') 3892 ) 3893 ); 3894 } 3895 3896 /** 3897 * Returns description of method parameters 3898 * 3899 * @return external_function_parameters 3900 */ 3901 public static function set_favourite_courses_parameters() { 3902 return new external_function_parameters( 3903 array( 3904 'courses' => new external_multiple_structure( 3905 new external_single_structure( 3906 array( 3907 'id' => new external_value(PARAM_INT, 'course ID'), 3908 'favourite' => new external_value(PARAM_BOOL, 'favourite status') 3909 ) 3910 ) 3911 ) 3912 ) 3913 ); 3914 } 3915 3916 /** 3917 * Set the course favourite status for an array of courses. 3918 * 3919 * @param array $courses List with course id's and favourite status. 3920 * @return array Array with an array of favourite courses. 3921 */ 3922 public static function set_favourite_courses( 3923 array $courses 3924 ) { 3925 global $USER; 3926 3927 $params = self::validate_parameters(self::set_favourite_courses_parameters(), 3928 array( 3929 'courses' => $courses 3930 ) 3931 ); 3932 3933 $warnings = []; 3934 3935 $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($USER->id)); 3936 3937 foreach ($params['courses'] as $course) { 3938 3939 $warning = []; 3940 3941 $favouriteexists = $ufservice->favourite_exists('core_course', 'courses', $course['id'], 3942 \context_course::instance($course['id'])); 3943 3944 if ($course['favourite']) { 3945 if (!$favouriteexists) { 3946 try { 3947 $ufservice->create_favourite('core_course', 'courses', $course['id'], 3948 \context_course::instance($course['id'])); 3949 } catch (Exception $e) { 3950 $warning['courseid'] = $course['id']; 3951 if ($e instanceof moodle_exception) { 3952 $warning['warningcode'] = $e->errorcode; 3953 } else { 3954 $warning['warningcode'] = $e->getCode(); 3955 } 3956 $warning['message'] = $e->getMessage(); 3957 $warnings[] = $warning; 3958 $warnings[] = $warning; 3959 } 3960 } else { 3961 $warning['courseid'] = $course['id']; 3962 $warning['warningcode'] = 'coursealreadyfavourited'; 3963 $warning['message'] = 'Course already favourited'; 3964 $warnings[] = $warning; 3965 } 3966 } else { 3967 if ($favouriteexists) { 3968 try { 3969 $ufservice->delete_favourite('core_course', 'courses', $course['id'], 3970 \context_course::instance($course['id'])); 3971 } catch (Exception $e) { 3972 $warning['courseid'] = $course['id']; 3973 if ($e instanceof moodle_exception) { 3974 $warning['warningcode'] = $e->errorcode; 3975 } else { 3976 $warning['warningcode'] = $e->getCode(); 3977 } 3978 $warning['message'] = $e->getMessage(); 3979 $warnings[] = $warning; 3980 $warnings[] = $warning; 3981 } 3982 } else { 3983 $warning['courseid'] = $course['id']; 3984 $warning['warningcode'] = 'cannotdeletefavourite'; 3985 $warning['message'] = 'Could not delete favourite status for course'; 3986 $warnings[] = $warning; 3987 } 3988 } 3989 } 3990 3991 return [ 3992 'warnings' => $warnings 3993 ]; 3994 } 3995 3996 /** 3997 * Returns description of method result value 3998 * 3999 * @return external_description 4000 */ 4001 public static function set_favourite_courses_returns() { 4002 return new external_single_structure( 4003 array( 4004 'warnings' => new external_warnings() 4005 ) 4006 ); 4007 } 4008 4009 /** 4010 * Returns description of method parameters 4011 * 4012 * @return external_function_parameters 4013 * @since Moodle 3.6 4014 */ 4015 public static function get_recent_courses_parameters() { 4016 return new external_function_parameters( 4017 array( 4018 'userid' => new external_value(PARAM_INT, 'id of the user, default to current user', VALUE_DEFAULT, 0), 4019 'limit' => new external_value(PARAM_INT, 'result set limit', VALUE_DEFAULT, 0), 4020 'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0), 4021 'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null) 4022 ) 4023 ); 4024 } 4025 4026 /** 4027 * Get last accessed courses adding additional course information like images. 4028 * 4029 * @param int $userid User id from which the courses will be obtained 4030 * @param int $limit Restrict result set to this amount 4031 * @param int $offset Skip this number of records from the start of the result set 4032 * @param string|null $sort SQL string for sorting 4033 * @return array List of courses 4034 * @throws invalid_parameter_exception 4035 */ 4036 public static function get_recent_courses(int $userid = 0, int $limit = 0, int $offset = 0, string $sort = null) { 4037 global $USER, $PAGE; 4038 4039 if (empty($userid)) { 4040 $userid = $USER->id; 4041 } 4042 4043 $params = self::validate_parameters(self::get_recent_courses_parameters(), 4044 array( 4045 'userid' => $userid, 4046 'limit' => $limit, 4047 'offset' => $offset, 4048 'sort' => $sort 4049 ) 4050 ); 4051 4052 $userid = $params['userid']; 4053 $limit = $params['limit']; 4054 $offset = $params['offset']; 4055 $sort = $params['sort']; 4056 4057 $usercontext = context_user::instance($userid); 4058 4059 self::validate_context($usercontext); 4060 4061 if ($userid != $USER->id and !has_capability('moodle/user:viewdetails', $usercontext)) { 4062 return array(); 4063 } 4064 4065 $courses = course_get_recent_courses($userid, $limit, $offset, $sort); 4066 4067 $renderer = $PAGE->get_renderer('core'); 4068 4069 $recentcourses = array_map(function($course) use ($renderer) { 4070 context_helper::preload_from_record($course); 4071 $context = context_course::instance($course->id); 4072 $isfavourite = !empty($course->component); 4073 $exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => $isfavourite]); 4074 return $exporter->export($renderer); 4075 }, $courses); 4076 4077 return $recentcourses; 4078 } 4079 4080 /** 4081 * Returns description of method result value 4082 * 4083 * @return external_description 4084 * @since Moodle 3.6 4085 */ 4086 public static function get_recent_courses_returns() { 4087 return new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Courses'); 4088 } 4089 4090 /** 4091 * Returns description of method parameters 4092 * 4093 * @return external_function_parameters 4094 */ 4095 public static function get_enrolled_users_by_cmid_parameters() { 4096 return new external_function_parameters([ 4097 'cmid' => new external_value(PARAM_INT, 'id of the course module', VALUE_REQUIRED), 4098 'groupid' => new external_value(PARAM_INT, 'id of the group', VALUE_DEFAULT, 0), 4099 ]); 4100 } 4101 4102 /** 4103 * Get all users in a course for a given cmid. 4104 * 4105 * @param int $cmid Course Module id from which the users will be obtained 4106 * @param int $groupid Group id from which the users will be obtained 4107 * @return array List of users 4108 * @throws invalid_parameter_exception 4109 */ 4110 public static function get_enrolled_users_by_cmid(int $cmid, int $groupid = 0) { 4111 global $PAGE; 4112 $warnings = []; 4113 4114 [ 4115 'cmid' => $cmid, 4116 'groupid' => $groupid, 4117 ] = self::validate_parameters(self::get_enrolled_users_by_cmid_parameters(), [ 4118 'cmid' => $cmid, 4119 'groupid' => $groupid, 4120 ]); 4121 4122 list($course, $cm) = get_course_and_cm_from_cmid($cmid); 4123 $coursecontext = context_course::instance($course->id); 4124 self::validate_context($coursecontext); 4125 4126 $enrolledusers = get_enrolled_users($coursecontext, '', $groupid); 4127 4128 $users = array_map(function ($user) use ($PAGE) { 4129 $user->fullname = fullname($user); 4130 $userpicture = new user_picture($user); 4131 $userpicture->size = 1; 4132 $user->profileimage = $userpicture->get_url($PAGE)->out(false); 4133 return $user; 4134 }, $enrolledusers); 4135 sort($users); 4136 4137 return [ 4138 'users' => $users, 4139 'warnings' => $warnings, 4140 ]; 4141 } 4142 4143 /** 4144 * Returns description of method result value 4145 * 4146 * @return external_description 4147 */ 4148 public static function get_enrolled_users_by_cmid_returns() { 4149 return new external_single_structure([ 4150 'users' => new external_multiple_structure(self::user_description()), 4151 'warnings' => new external_warnings(), 4152 ]); 4153 } 4154 4155 /** 4156 * Create user return value description. 4157 * 4158 * @return external_description 4159 */ 4160 public static function user_description() { 4161 $userfields = array( 4162 'id' => new external_value(core_user::get_property_type('id'), 'ID of the user'), 4163 'profileimage' => new external_value(PARAM_URL, 'The location of the users larger image', VALUE_OPTIONAL), 4164 'fullname' => new external_value(PARAM_TEXT, 'The full name of the user', VALUE_OPTIONAL), 4165 'firstname' => new external_value( 4166 core_user::get_property_type('firstname'), 4167 'The first name(s) of the user', 4168 VALUE_OPTIONAL), 4169 'lastname' => new external_value( 4170 core_user::get_property_type('lastname'), 4171 'The family name of the user', 4172 VALUE_OPTIONAL), 4173 ); 4174 return new external_single_structure($userfields); 4175 } 4176 4177 /** 4178 * Returns description of method parameters. 4179 * 4180 * @return external_function_parameters 4181 */ 4182 public static function add_content_item_to_user_favourites_parameters() { 4183 return new external_function_parameters([ 4184 'componentname' => new external_value(PARAM_TEXT, 4185 'frankenstyle name of the component to which the content item belongs', VALUE_REQUIRED), 4186 'contentitemid' => new external_value(PARAM_INT, 'id of the content item', VALUE_REQUIRED, '', NULL_NOT_ALLOWED) 4187 ]); 4188 } 4189 4190 /** 4191 * Add a content item to a user's favourites. 4192 * 4193 * @param string $componentname the name of the component from which this content item originates. 4194 * @param int $contentitemid the id of the content item. 4195 * @return stdClass the exporter content item. 4196 */ 4197 public static function add_content_item_to_user_favourites(string $componentname, int $contentitemid) { 4198 global $USER; 4199 4200 [ 4201 'componentname' => $componentname, 4202 'contentitemid' => $contentitemid, 4203 ] = self::validate_parameters(self::add_content_item_to_user_favourites_parameters(), 4204 [ 4205 'componentname' => $componentname, 4206 'contentitemid' => $contentitemid, 4207 ] 4208 ); 4209 4210 self::validate_context(context_user::instance($USER->id)); 4211 4212 $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service(); 4213 4214 return $contentitemservice->add_to_user_favourites($USER, $componentname, $contentitemid); 4215 } 4216 4217 /** 4218 * Returns description of method result value. 4219 * 4220 * @return external_description 4221 */ 4222 public static function add_content_item_to_user_favourites_returns() { 4223 return \core_course\local\exporters\course_content_item_exporter::get_read_structure(); 4224 } 4225 4226 /** 4227 * Returns description of method parameters. 4228 * 4229 * @return external_function_parameters 4230 */ 4231 public static function remove_content_item_from_user_favourites_parameters() { 4232 return new external_function_parameters([ 4233 'componentname' => new external_value(PARAM_TEXT, 4234 'frankenstyle name of the component to which the content item belongs', VALUE_REQUIRED), 4235 'contentitemid' => new external_value(PARAM_INT, 'id of the content item', VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 4236 ]); 4237 } 4238 4239 /** 4240 * Remove a content item from a user's favourites. 4241 * 4242 * @param string $componentname the name of the component from which this content item originates. 4243 * @param int $contentitemid the id of the content item. 4244 * @return stdClass the exported content item. 4245 */ 4246 public static function remove_content_item_from_user_favourites(string $componentname, int $contentitemid) { 4247 global $USER; 4248 4249 [ 4250 'componentname' => $componentname, 4251 'contentitemid' => $contentitemid, 4252 ] = self::validate_parameters(self::remove_content_item_from_user_favourites_parameters(), 4253 [ 4254 'componentname' => $componentname, 4255 'contentitemid' => $contentitemid, 4256 ] 4257 ); 4258 4259 self::validate_context(context_user::instance($USER->id)); 4260 4261 $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service(); 4262 4263 return $contentitemservice->remove_from_user_favourites($USER, $componentname, $contentitemid); 4264 } 4265 4266 /** 4267 * Returns description of method result value. 4268 * 4269 * @return external_description 4270 */ 4271 public static function remove_content_item_from_user_favourites_returns() { 4272 return \core_course\local\exporters\course_content_item_exporter::get_read_structure(); 4273 } 4274 4275 /** 4276 * Returns description of method result value 4277 * 4278 * @return external_description 4279 */ 4280 public static function get_course_content_items_returns() { 4281 return new external_single_structure([ 4282 'content_items' => new external_multiple_structure( 4283 \core_course\local\exporters\course_content_item_exporter::get_read_structure() 4284 ), 4285 ]); 4286 } 4287 4288 /** 4289 * Returns description of method parameters 4290 * 4291 * @return external_function_parameters 4292 */ 4293 public static function get_course_content_items_parameters() { 4294 return new external_function_parameters([ 4295 'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED), 4296 ]); 4297 } 4298 4299 /** 4300 * Given a course ID fetch all accessible modules for that course 4301 * 4302 * @param int $courseid The course we want to fetch the modules for 4303 * @return array Contains array of modules and their metadata 4304 */ 4305 public static function get_course_content_items(int $courseid) { 4306 global $USER; 4307 4308 [ 4309 'courseid' => $courseid, 4310 ] = self::validate_parameters(self::get_course_content_items_parameters(), [ 4311 'courseid' => $courseid, 4312 ]); 4313 4314 $coursecontext = context_course::instance($courseid); 4315 self::validate_context($coursecontext); 4316 $course = get_course($courseid); 4317 4318 $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service(); 4319 4320 $contentitems = $contentitemservice->get_content_items_for_user_in_course($USER, $course); 4321 return ['content_items' => $contentitems]; 4322 } 4323 4324 /** 4325 * Returns description of method parameters. 4326 * 4327 * @return external_function_parameters 4328 */ 4329 public static function toggle_activity_recommendation_parameters() { 4330 return new external_function_parameters([ 4331 'area' => new external_value(PARAM_TEXT, 'The favourite area (itemtype)', VALUE_REQUIRED), 4332 'id' => new external_value(PARAM_INT, 'id of the activity or whatever', VALUE_REQUIRED), 4333 ]); 4334 } 4335 4336 /** 4337 * Update the recommendation for an activity item. 4338 * 4339 * @param string $area identifier for this activity. 4340 * @param int $id Associated id. This is needed in conjunction with the area to find the recommendation. 4341 * @return array some warnings or something. 4342 */ 4343 public static function toggle_activity_recommendation(string $area, int $id): array { 4344 ['area' => $area, 'id' => $id] = self::validate_parameters(self::toggle_activity_recommendation_parameters(), 4345 ['area' => $area, 'id' => $id]); 4346 4347 $context = context_system::instance(); 4348 self::validate_context($context); 4349 4350 require_capability('moodle/course:recommendactivity', $context); 4351 4352 $manager = \core_course\local\factory\content_item_service_factory::get_content_item_service(); 4353 4354 $status = $manager->toggle_recommendation($area, $id); 4355 return ['id' => $id, 'area' => $area, 'status' => $status]; 4356 } 4357 4358 /** 4359 * Returns warnings. 4360 * 4361 * @return external_description 4362 */ 4363 public static function toggle_activity_recommendation_returns() { 4364 return new external_single_structure( 4365 [ 4366 'id' => new external_value(PARAM_INT, 'id of the activity or whatever'), 4367 'area' => new external_value(PARAM_TEXT, 'The favourite area (itemtype)'), 4368 'status' => new external_value(PARAM_BOOL, 'If created or deleted'), 4369 ] 4370 ); 4371 } 4372 4373 /** 4374 * Returns description of method parameters 4375 * 4376 * @return external_function_parameters 4377 */ 4378 public static function get_activity_chooser_footer_parameters() { 4379 return new external_function_parameters([ 4380 'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED), 4381 'sectionid' => new external_value(PARAM_INT, 'ID of the section', VALUE_REQUIRED), 4382 ]); 4383 } 4384 4385 /** 4386 * Given a course ID we need to build up a footre for the chooser. 4387 * 4388 * @param int $courseid The course we want to fetch the modules for 4389 * @param int $sectionid The section we want to fetch the modules for 4390 * @return array 4391 */ 4392 public static function get_activity_chooser_footer(int $courseid, int $sectionid) { 4393 [ 4394 'courseid' => $courseid, 4395 'sectionid' => $sectionid, 4396 ] = self::validate_parameters(self::get_activity_chooser_footer_parameters(), [ 4397 'courseid' => $courseid, 4398 'sectionid' => $sectionid, 4399 ]); 4400 4401 $coursecontext = context_course::instance($courseid); 4402 self::validate_context($coursecontext); 4403 4404 $pluginswithfunction = get_plugins_with_function('custom_chooser_footer', 'lib.php'); 4405 if ($pluginswithfunction) { 4406 foreach ($pluginswithfunction as $plugintype => $plugins) { 4407 foreach ($plugins as $pluginfunction) { 4408 $footerdata = $pluginfunction($courseid, $sectionid); 4409 break; // Only a single plugin can modify the footer. 4410 } 4411 break; // Only a single plugin can modify the footer. 4412 } 4413 return [ 4414 'footer' => true, 4415 'customfooterjs' => $footerdata->get_footer_js_file(), 4416 'customfootertemplate' => $footerdata->get_footer_template(), 4417 'customcarouseltemplate' => $footerdata->get_carousel_template(), 4418 ]; 4419 } else { 4420 return [ 4421 'footer' => false, 4422 ]; 4423 } 4424 } 4425 4426 /** 4427 * Returns description of method result value 4428 * 4429 * @return external_description 4430 */ 4431 public static function get_activity_chooser_footer_returns() { 4432 return new external_single_structure( 4433 [ 4434 'footer' => new external_value(PARAM_BOOL, 'Is a footer being return by this request?', VALUE_REQUIRED), 4435 'customfooterjs' => new external_value(PARAM_RAW, 'The path to the plugin JS file', VALUE_OPTIONAL), 4436 'customfootertemplate' => new external_value(PARAM_RAW, 'The prerendered footer', VALUE_OPTIONAL), 4437 'customcarouseltemplate' => new external_value(PARAM_RAW, 'Either "" or the prerendered carousel page', 4438 VALUE_OPTIONAL), 4439 ] 4440 ); 4441 } 4442 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body