mirror of
https://github.com/moodle/moodle.git
synced 2025-04-25 10:26:17 +02:00
MDL-51324 forms: Add a new course selector
This is a squashed commit containing a number of changes: This is an ajax driven course selector that has searching etc. It can select single, or multiple courses. Make course selector accept a list of courses to exclude courseselector - lookup coursename on setValue Use the get_course_display_name_in_list function to generate the course names Add a throttle to auto-complete to reduce spamming the server Do a single query to fetch all the courses in the mform element when validation fails Fix core course search function to return results when there are less than 2 chars in the query. Handle setData with an empty array in new course selector
This commit is contained in:
parent
9502c7f539
commit
235ef57a3d
@ -2135,7 +2135,11 @@ class core_course_external extends external_api {
|
||||
(search, modulelist (only admins), blocklist (only admins), tagid)'),
|
||||
'criteriavalue' => new external_value(PARAM_RAW, 'criteria value'),
|
||||
'page' => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0),
|
||||
'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0)
|
||||
'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
|
||||
'requiredcapabilities' => new external_multiple_structure(
|
||||
new external_value(PARAM_CAPABILITY, 'Capability string used to filter courses by permission'),
|
||||
VALUE_OPTIONAL
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -2147,11 +2151,16 @@ class core_course_external extends external_api {
|
||||
* @param string $criteriavalue Criteria value
|
||||
* @param int $page Page number (for pagination)
|
||||
* @param int $perpage Items per page
|
||||
* @param array $requiredcapabilities Optional list of required capabilities (used to filter the list).
|
||||
* @return array of course objects and warnings
|
||||
* @since Moodle 3.0
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function search_courses($criterianame, $criteriavalue, $page=0, $perpage=0) {
|
||||
public static function search_courses($criterianame,
|
||||
$criteriavalue,
|
||||
$page=0,
|
||||
$perpage=0,
|
||||
$requiredcapabilities=array()) {
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/coursecatlib.php');
|
||||
|
||||
@ -2161,7 +2170,8 @@ class core_course_external extends external_api {
|
||||
'criterianame' => $criterianame,
|
||||
'criteriavalue' => $criteriavalue,
|
||||
'page' => $page,
|
||||
'perpage' => $perpage
|
||||
'perpage' => $perpage,
|
||||
'requiredcapabilities' => $requiredcapabilities
|
||||
);
|
||||
$params = self::validate_parameters(self::search_courses_parameters(), $parameters);
|
||||
|
||||
@ -2194,8 +2204,8 @@ class core_course_external extends external_api {
|
||||
}
|
||||
|
||||
// Search the courses.
|
||||
$courses = coursecat::search_courses($searchcriteria, $options);
|
||||
$totalcount = coursecat::search_courses_count($searchcriteria);
|
||||
$courses = coursecat::search_courses($searchcriteria, $options, $params['requiredcapabilities']);
|
||||
$totalcount = coursecat::search_courses_count($searchcriteria, $options, $params['requiredcapabilities']);
|
||||
|
||||
$finalcourses = array();
|
||||
$categoriescache = array();
|
||||
@ -2244,10 +2254,12 @@ class core_course_external extends external_api {
|
||||
list($summary, $summaryformat) =
|
||||
external_format_text($course->summary, $course->summaryformat, $coursecontext->id, 'course', 'summary', null);
|
||||
|
||||
$displayname = get_course_display_name_for_list($course);
|
||||
$coursereturns = array();
|
||||
$coursereturns['id'] = $course->id;
|
||||
$coursereturns['fullname'] = $course->get_formatted_fullname();
|
||||
$coursereturns['shortname'] = $course->get_formatted_shortname();
|
||||
$coursereturns['fullname'] = external_format_string($course->fullname, $coursecontext->id);
|
||||
$coursereturns['displayname'] = external_format_string($displayname, $coursecontext->id);
|
||||
$coursereturns['shortname'] = external_format_string($course->shortname, $coursecontext->id);
|
||||
$coursereturns['categoryid'] = $course->category;
|
||||
$coursereturns['categoryname'] = $category->name;
|
||||
$coursereturns['summary'] = $summary;
|
||||
@ -2281,6 +2293,7 @@ class core_course_external extends external_api {
|
||||
array(
|
||||
'id' => new external_value(PARAM_INT, 'course id'),
|
||||
'fullname' => new external_value(PARAM_TEXT, 'course full name'),
|
||||
'displayname' => new external_value(PARAM_TEXT, 'course display name'),
|
||||
'shortname' => new external_value(PARAM_TEXT, 'course short name'),
|
||||
'categoryid' => new external_value(PARAM_INT, 'category id'),
|
||||
'categoryname' => new external_value(PARAM_TEXT, 'category name'),
|
||||
|
2
lib/amd/build/form-autocomplete.min.js
vendored
2
lib/amd/build/form-autocomplete.min.js
vendored
File diff suppressed because one or more lines are too long
1
lib/amd/build/form-course-selector.min.js
vendored
Normal file
1
lib/amd/build/form-course-selector.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
define(["core/ajax","jquery"],function(a,b){return{processResults:function(a,c){var d=[],e=0,f=String(b(a).data("exclude")).split(",");for(e=0;e<c.courses.length;e++)-1===f.indexOf(String(c.courses[e].id))&&d.push({value:c.courses[e].id,label:c.courses[e].displayname});return d},transport:function(c,d,e,f){var g=b(c).data("requiredcapabilities");g=""!==g.trim()?g.split(","):[];var h=null;"undefined"==typeof d&&(d="");var i={criterianame:"search",criteriavalue:d,page:0,perpage:100,requiredcapabilities:g};return h=a.call([{methodname:"core_course_search_courses",args:i}]),h[0].done(e),h[0].fail(f),h}}});
|
@ -747,11 +747,22 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
// If this field uses ajax, set it up.
|
||||
if (options.ajax) {
|
||||
require([options.ajax], function(ajaxHandler) {
|
||||
var throttleTimeout = null;
|
||||
var handler = function(e) {
|
||||
updateAjax(e, options, state, originalSelect, ajaxHandler);
|
||||
};
|
||||
|
||||
// For input events, we do not want to trigger many, many updates.
|
||||
var throttledHandler = function(e) {
|
||||
if (throttleTimeout !== null) {
|
||||
window.clearTimeout(throttleTimeout);
|
||||
throttleTimeout = null;
|
||||
}
|
||||
throttleTimeout = window.setTimeout(handler.bind(this, e), 300);
|
||||
};
|
||||
// Trigger an ajax update after the text field value changes.
|
||||
inputElement.on("input keypress", handler);
|
||||
inputElement.on("input keypress", throttledHandler);
|
||||
|
||||
var arrowElement = $(document.getElementById(state.downArrowId));
|
||||
arrowElement.on("click", handler);
|
||||
});
|
||||
|
76
lib/amd/src/form-course-selector.js
Normal file
76
lib/amd/src/form-course-selector.js
Normal file
@ -0,0 +1,76 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Course selector adaptor for auto-complete form element.
|
||||
*
|
||||
* @module core/form-course-selector
|
||||
* @class form-course-selector
|
||||
* @package core
|
||||
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 3.1
|
||||
*/
|
||||
define(['core/ajax', 'jquery'], function(ajax, $) {
|
||||
|
||||
return /** @alias module:core/form-course-selector */ {
|
||||
// Public variables and functions.
|
||||
processResults: function(selector, data) {
|
||||
// Mangle the results into an array of objects.
|
||||
var results = [], i = 0;
|
||||
var excludelist = String($(selector).data('exclude')).split(',');
|
||||
|
||||
for (i = 0; i < data.courses.length; i++) {
|
||||
if (excludelist.indexOf(String(data.courses[i].id)) === -1) {
|
||||
results.push({ value: data.courses[i].id, label: data.courses[i].displayname });
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
transport: function(selector, query, success, failure) {
|
||||
// Parse some data-attributes from the form element.
|
||||
var requiredcapabilities = $(selector).data('requiredcapabilities');
|
||||
if (requiredcapabilities.trim() !== "") {
|
||||
requiredcapabilities = requiredcapabilities.split(',');
|
||||
} else {
|
||||
requiredcapabilities = [];
|
||||
}
|
||||
// Build the query.
|
||||
var promise = null;
|
||||
|
||||
if (typeof query === "undefined") {
|
||||
query = '';
|
||||
}
|
||||
|
||||
var searchargs = {
|
||||
criterianame: 'search',
|
||||
criteriavalue: query,
|
||||
page: 0,
|
||||
perpage: 100,
|
||||
requiredcapabilities: requiredcapabilities
|
||||
};
|
||||
// Go go go!
|
||||
promise = ajax.call([{
|
||||
methodname: 'core_course_search_courses', args: searchargs
|
||||
}]);
|
||||
|
||||
promise[0].done(success);
|
||||
promise[0].fail(failure);
|
||||
|
||||
return promise;
|
||||
}
|
||||
};
|
||||
});
|
@ -1276,16 +1276,19 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
|
||||
* - tagid - id of tag
|
||||
* @param array $options display options, same as in get_courses() except 'recursive' is ignored -
|
||||
* search is always category-independent
|
||||
* @param array $requiredcapabilites List of capabilities required to see return course.
|
||||
* @return course_in_list[]
|
||||
*/
|
||||
public static function search_courses($search, $options = array()) {
|
||||
public static function search_courses($search, $options = array(), $requiredcapabilities = array()) {
|
||||
global $DB;
|
||||
$offset = !empty($options['offset']) ? $options['offset'] : 0;
|
||||
$limit = !empty($options['limit']) ? $options['limit'] : null;
|
||||
$sortfields = !empty($options['sort']) ? $options['sort'] : array('sortorder' => 1);
|
||||
|
||||
$coursecatcache = cache::make('core', 'coursecat');
|
||||
$cachekey = 's-'. serialize($search + array('sort' => $sortfields));
|
||||
$cachekey = 's-'. serialize(
|
||||
$search + array('sort' => $sortfields) + array('requiredcapabilities' => $requiredcapabilities)
|
||||
);
|
||||
$cntcachekey = 'scnt-'. serialize($search);
|
||||
|
||||
$ids = $coursecatcache->get($cachekey);
|
||||
@ -1315,11 +1318,16 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
|
||||
$preloadcoursecontacts = !empty($options['coursecontacts']);
|
||||
unset($options['coursecontacts']);
|
||||
|
||||
if (!empty($search['search'])) {
|
||||
// Empty search string will return all results.
|
||||
if (!isset($search['search'])) {
|
||||
$search['search'] = '';
|
||||
}
|
||||
|
||||
if (empty($search['blocklist']) && empty($search['modulelist']) && empty($search['tagid'])) {
|
||||
// Search courses that have specified words in their names/summaries.
|
||||
$searchterms = preg_split('|\s+|', trim($search['search']), 0, PREG_SPLIT_NO_EMPTY);
|
||||
$searchterms = array_filter($searchterms, create_function('$v', 'return strlen($v) > 1;'));
|
||||
$courselist = get_courses_search($searchterms, 'c.sortorder ASC', 0, 9999999, $totalcount);
|
||||
|
||||
$courselist = get_courses_search($searchterms, 'c.sortorder ASC', 0, 9999999, $totalcount, $requiredcapabilities);
|
||||
self::sort_records($courselist, $sortfields);
|
||||
$coursecatcache->set($cachekey, array_keys($courselist));
|
||||
$coursecatcache->set($cntcachekey, $totalcount);
|
||||
@ -1365,6 +1373,15 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
|
||||
return array();
|
||||
}
|
||||
$courselist = self::get_course_records($where, $params, $options, true);
|
||||
if (!empty($requiredcapabilities)) {
|
||||
foreach ($courselist as $key => $course) {
|
||||
context_helper::preload_from_record($course);
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
if (!has_all_capabilities($requiredcapabilities, $coursecontext)) {
|
||||
unset($courselist[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
self::sort_records($courselist, $sortfields);
|
||||
$coursecatcache->set($cachekey, array_keys($courselist));
|
||||
$coursecatcache->set($cntcachekey, count($courselist));
|
||||
@ -1397,11 +1414,12 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
|
||||
* @param array $search search criteria, see method search_courses() for more details
|
||||
* @param array $options display options. They do not affect the result but
|
||||
* the 'sort' property is used in cache key for storing list of course ids
|
||||
* @param array $requiredcapabilites List of capabilities required to see return course.
|
||||
* @return int
|
||||
*/
|
||||
public static function search_courses_count($search, $options = array()) {
|
||||
public static function search_courses_count($search, $options = array(), $requiredcapabilities = array()) {
|
||||
$coursecatcache = cache::make('core', 'coursecat');
|
||||
$cntcachekey = 'scnt-'. serialize($search);
|
||||
$cntcachekey = 'scnt-'. serialize($search) . serialize($requiredcapabilities);
|
||||
if (($cnt = $coursecatcache->get($cntcachekey)) === false) {
|
||||
// Cached value not found. Retrieve ALL courses and return their count.
|
||||
unset($options['offset']);
|
||||
@ -1409,7 +1427,7 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
|
||||
unset($options['summary']);
|
||||
unset($options['coursecontacts']);
|
||||
$options['idonly'] = true;
|
||||
$courses = self::search_courses($search, $options);
|
||||
$courses = self::search_courses($search, $options, $requiredcapabilities);
|
||||
$cnt = count($courses);
|
||||
}
|
||||
return $cnt;
|
||||
|
@ -741,9 +741,11 @@ function get_courses_page($categoryid="all", $sort="c.sortorder ASC", $fields="c
|
||||
* @param int $page The page number to get
|
||||
* @param int $recordsperpage The number of records per page
|
||||
* @param int $totalcount Passed in by reference.
|
||||
* @param array $requiredcapabilities Extra list of capabilities used to filter courses
|
||||
* @return object {@link $COURSE} records
|
||||
*/
|
||||
function get_courses_search($searchterms, $sort, $page, $recordsperpage, &$totalcount) {
|
||||
function get_courses_search($searchterms, $sort, $page, $recordsperpage, &$totalcount,
|
||||
$requiredcapabilities = array()) {
|
||||
global $CFG, $DB;
|
||||
|
||||
if ($DB->sql_regex_supported()) {
|
||||
@ -798,8 +800,7 @@ function get_courses_search($searchterms, $sort, $page, $recordsperpage, &$total
|
||||
}
|
||||
|
||||
if (empty($searchcond)) {
|
||||
$totalcount = 0;
|
||||
return array();
|
||||
$searchcond = array('1 = 1');
|
||||
}
|
||||
|
||||
$searchcond = implode(" AND ", $searchcond);
|
||||
@ -823,11 +824,14 @@ function get_courses_search($searchterms, $sort, $page, $recordsperpage, &$total
|
||||
|
||||
$rs = $DB->get_recordset_sql($sql, $params);
|
||||
foreach($rs as $course) {
|
||||
if (!$course->visible) {
|
||||
// preload contexts only for hidden courses or courses we need to return
|
||||
context_helper::preload_from_record($course);
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
if (!has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
|
||||
// Preload contexts only for hidden courses or courses we need to return.
|
||||
context_helper::preload_from_record($course);
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
if (!$course->visible && !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
|
||||
continue;
|
||||
}
|
||||
if (!empty($requiredcapabilities)) {
|
||||
if (!has_all_capabilities($requiredcapabilities, $coursecontext)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -669,6 +669,7 @@ $functions = array(
|
||||
'description' => 'Return course details',
|
||||
'type' => 'read',
|
||||
'capabilities'=> 'moodle/course:view,moodle/course:update,moodle/course:viewhiddencourses',
|
||||
'ajax' => true,
|
||||
),
|
||||
|
||||
'core_course_search_courses' => array(
|
||||
@ -678,6 +679,7 @@ $functions = array(
|
||||
'description' => 'Search courses by (name, module, block, tag)',
|
||||
'type' => 'read',
|
||||
'capabilities' => '',
|
||||
'ajax' => true,
|
||||
),
|
||||
|
||||
'moodle_course_create_courses' => array(
|
||||
|
142
lib/form/course.php
Normal file
142
lib/form/course.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
/**
|
||||
* Course selector field.
|
||||
*
|
||||
* Allows auto-complete ajax searching for courses and can restrict by enrolment, permissions, viewhidden...
|
||||
*
|
||||
* @package core_form
|
||||
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/form/autocomplete.php');
|
||||
|
||||
/**
|
||||
* Form field type for choosing a course.
|
||||
*
|
||||
* Allows auto-complete ajax searching for courses and can restrict by enrolment, permissions, viewhidden...
|
||||
*
|
||||
* @package core_form
|
||||
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class MoodleQuickForm_course extends MoodleQuickForm_autocomplete {
|
||||
|
||||
/**
|
||||
* @var array $exclude Exclude a list of courses from the list (e.g. the current course).
|
||||
*/
|
||||
protected $exclude = array();
|
||||
|
||||
/**
|
||||
* @var boolean $allowmultiple Allow selecting more than one course.
|
||||
*/
|
||||
protected $multiple = false;
|
||||
|
||||
/**
|
||||
* @var array $requiredcapabilities Array of extra capabilities to check at the course context.
|
||||
*/
|
||||
protected $requiredcapabilities = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $elementname Element name
|
||||
* @param mixed $elementlabel Label(s) for an element
|
||||
* @param array $options Options to control the element's display
|
||||
* Valid options are:
|
||||
* 'multiple' - boolean multi select
|
||||
* 'exclude' - array or int, list of course ids to never show
|
||||
* 'requiredcapabilities' - array of capabilities. Uses ANY to combine them.
|
||||
*/
|
||||
public function __construct($elementname = null, $elementlabel = null, $options = array()) {
|
||||
if (isset($options['multiple'])) {
|
||||
$this->multiple = $options['multiple'];
|
||||
}
|
||||
if (isset($options['exclude'])) {
|
||||
$this->exclude = $options['exclude'];
|
||||
if (!is_array($this->exclude)) {
|
||||
$this->exclude = array($this->exclude);
|
||||
}
|
||||
}
|
||||
if (isset($options['requiredcapabilities'])) {
|
||||
$this->requiredcapabilities = $options['requiredcapabilities'];
|
||||
}
|
||||
|
||||
$validattributes = array(
|
||||
'ajax' => 'core/form-course-selector',
|
||||
'data-requiredcapabilities' => implode(',', $this->requiredcapabilities),
|
||||
'data-exclude' => implode(',', $this->exclude)
|
||||
);
|
||||
if ($this->multiple) {
|
||||
$validattributes['multiple'] = 'multiple';
|
||||
}
|
||||
|
||||
parent::__construct($elementname, $elementlabel, array(), $validattributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of this element. If values can be added or are unknown, we will
|
||||
* make sure they exist in the options array.
|
||||
* @param string|array $value The value to set.
|
||||
* @return boolean
|
||||
*/
|
||||
public function setValue($value) {
|
||||
global $DB;
|
||||
$values = (array) $value;
|
||||
$coursestofetch = array();
|
||||
|
||||
foreach ($values as $onevalue) {
|
||||
if ((!$this->optionExists($onevalue)) &&
|
||||
($onevalue !== '_qf__force_multiselect_submission')) {
|
||||
array_push($coursestofetch, $onevalue);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($coursestofetch)) {
|
||||
return $this->setSelected(array());
|
||||
}
|
||||
|
||||
// There is no API function to load a list of course from a list of ids.
|
||||
$ctxselect = context_helper::get_preload_record_columns_sql('ctx');
|
||||
$fields = array('c.id', 'c.category', 'c.sortorder',
|
||||
'c.shortname', 'c.fullname', 'c.idnumber',
|
||||
'c.startdate', 'c.visible', 'c.cacherev');
|
||||
list($whereclause, $params) = $DB->get_in_or_equal($coursestofetch, SQL_PARAMS_NAMED, 'id');
|
||||
|
||||
$sql = "SELECT ". join(',', $fields). ", $ctxselect
|
||||
FROM {course} c
|
||||
JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = :contextcourse
|
||||
WHERE c.id ". $whereclause." ORDER BY c.sortorder";
|
||||
$list = $DB->get_records_sql($sql, array('contextcourse' => CONTEXT_COURSE) + $params);
|
||||
|
||||
$coursestoselect = array();
|
||||
foreach ($list as $course) {
|
||||
context_helper::preload_from_record($course);
|
||||
// Make sure we can see the course.
|
||||
if (!$course->visible && !has_capability('moodle/course:viewhiddencourses', context_course::instance($course->id))) {
|
||||
continue;
|
||||
}
|
||||
$label = get_course_display_name_for_list($course);
|
||||
$this->addOption($label, $course->id);
|
||||
array_push($coursestoselect, $course->id);
|
||||
}
|
||||
return $this->setSelected($coursestoselect);
|
||||
}
|
||||
}
|
@ -2995,6 +2995,7 @@ MoodleQuickForm::registerElementType('advcheckbox', "$CFG->libdir/form/advcheckb
|
||||
MoodleQuickForm::registerElementType('autocomplete', "$CFG->libdir/form/autocomplete.php", 'MoodleQuickForm_autocomplete');
|
||||
MoodleQuickForm::registerElementType('button', "$CFG->libdir/form/button.php", 'MoodleQuickForm_button');
|
||||
MoodleQuickForm::registerElementType('cancel', "$CFG->libdir/form/cancel.php", 'MoodleQuickForm_cancel');
|
||||
MoodleQuickForm::registerElementType('course', "$CFG->libdir/form/course.php", 'MoodleQuickForm_course');
|
||||
MoodleQuickForm::registerElementType('searchableselector', "$CFG->libdir/form/searchableselector.php", 'MoodleQuickForm_searchableselector');
|
||||
MoodleQuickForm::registerElementType('checkbox', "$CFG->libdir/form/checkbox.php", 'MoodleQuickForm_checkbox');
|
||||
MoodleQuickForm::registerElementType('date_selector', "$CFG->libdir/form/dateselector.php", 'MoodleQuickForm_date_selector');
|
||||
|
@ -3,6 +3,11 @@ information provided here is intended especially for developers.
|
||||
|
||||
=== 3.1 ===
|
||||
|
||||
* Webservice function core_course_search_courses now returns results when the search string
|
||||
is less than 2 chars long.
|
||||
* Webservice function core_course_search_courses accepts a new parameter 'requiredcapabilities' to filter the results
|
||||
by the capabilities of the current user.
|
||||
* New mform element 'course' handles thousands of courses with good performance and usability.
|
||||
* The redirect() function will now redirect immediately if output has not
|
||||
already started. Messages will be displayed on the subsequent page using
|
||||
session notifications. The type of message output can be configured using the
|
||||
|
Loading…
x
Reference in New Issue
Block a user