This commit is contained in:
Adrian Greeve
2025-08-25 16:00:31 +08:00
20 changed files with 1613 additions and 1388 deletions
@@ -0,0 +1,11 @@
issueNumber: MDL-82322
notes:
core_course:
- message: |
The following classes have been renamed and now support autoloading.
Existing classes are currently unaffected.
| Old class name | New class name |
| --- | --- |
| `\course_request` | `\core_course\course_request` |
type: improved
@@ -22,6 +22,8 @@
*/
namespace tool_moodlenet\local;
use core_course\dndupload_handler;
/**
* The import_handler_registry class.
*
@@ -113,9 +115,7 @@ class import_handler_registry {
protected function populate_handlers() {
// Generate a dndupload_handler object, just so we can call ->is_known_type() on the types being registered by plugins.
// We must vet each type which is reported to be handled against the list of known, supported types.
global $CFG;
require_once($CFG->dirroot . '/course/dnduploadlib.php');
$dndhandlers = new \dndupload_handler($this->course);
$dndhandlers = new dndupload_handler($this->course);
// Get the list of mods enabled at site level first. We need to cross check this.
$pluginman = \core_plugin_manager::instance();
@@ -185,4 +185,3 @@ class import_handler_registry {
$this->filehandlers[$extension][] = ['module' => $module, 'message' => $message];
}
}
+1 -3
View File
@@ -24,6 +24,7 @@
*/
use core\exception\moodle_exception;
use core_course\course_request;
/**
* Class to store, cache, render and manage course category
@@ -3117,9 +3118,6 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr
* @return bool
*/
public function can_request_course() {
global $CFG;
require_once($CFG->dirroot . '/course/lib.php');
return course_request::can_request($this->get_context());
}
+467
View File
@@ -0,0 +1,467 @@
<?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/>.
namespace core_course;
use core\context;
use core\context\course as context_course;
use core\context\coursecat as context_coursecat;
use core\context\system as context_system;
use core\exception\coding_exception;
use core\exception\moodle_exception;
use core_course_category;
use restore_dbops;
use stdClass;
/**
* This class pertains to course requests and contains methods associated with
* create, approving, and removing course requests.
*
* Please note we do not allow embedded images here because there is no context
* to store them with proper access control.
*
* @package core_course
* @copyright 2009 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.0
*
* @property-read int $id
* @property-read string $fullname
* @property-read string $shortname
* @property-read string $summary
* @property-read int $summaryformat
* @property-read int $summarytrust
* @property-read string $reason
* @property-read int $requester
*/
class course_request {
/**
* This is the stdClass that stores the properties for the course request
* and is externally accessed through the __get magic method
* @var stdClass
*/
protected $properties;
/**
* An array of options for the summary editor used by course request forms.
*
* This is initially set by {@see summary_editor_options()}
* @var array
*/
protected static $summaryeditoroptions;
/**
* Static function to prepare the summary editor for working with a course request.
*
* @param null|stdClass $data Optional, an object containing the default values
* for the form, these may be modified when preparing the
* editor so this should be called before creating the form
* @return stdClass An object that can be used to set the default values for
* an mforms form
*/
public static function prepare($data = null) {
if ($data === null) {
$data = new stdClass();
}
$data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options());
return $data;
}
/**
* Static function to create a new course request when passed an array of properties
* for it.
*
* This function also handles saving any files that may have been used in the editor
*
* @param stdClass $data
* @return course_request The newly created course request
*/
public static function create($data) {
global $USER, $DB, $CFG;
$data->requester = $USER->id;
// Setting the default category if none set.
if (empty($data->category) || !empty($CFG->lockrequestcategory)) {
$data->category = $CFG->defaultrequestcategory;
}
// Summary is a required field so copy the text over.
$data->summary = $data->summary_editor['text'];
$data->summaryformat = $data->summary_editor['format'];
$data->id = $DB->insert_record('course_request', $data);
// Create a new course_request object and return it.
$request = new course_request($data);
// Notify the admin if required.
if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) {
$a = new stdClass();
$a->link = "$CFG->wwwroot/course/pending.php";
$a->user = fullname($USER);
$a->shortname = s($data->shortname) ?? '';
$a->fullname = s($data->fullname) ?? '';
$a->category = s($data->category) ?? '';
$a->reason = format_text($data->reason, FORMAT_PLAIN) ?? '';
$subject = get_string('courserequest');
$message = get_string('courserequestnotifyemail', 'admin', $a);
foreach ($users as $user) {
$request->notify($user, $USER, 'courserequested', $subject, $message);
}
}
return $request;
}
/**
* Returns an array of options to use with a summary editor
*
* @uses course_request::$summaryeditoroptions
* @return array An array of options to use with the editor
*/
public static function summary_editor_options() {
global $CFG;
if (self::$summaryeditoroptions === null) {
self::$summaryeditoroptions = ['maxfiles' => 0, 'maxbytes' => 0];
}
return self::$summaryeditoroptions;
}
/**
* Loads the properties for this course request object. Id is required and if
* only id is provided then we load the rest of the properties from the database
*
* @param stdClass|int $properties Either an object containing properties
* or the course_request id to load
*/
public function __construct($properties) {
global $DB;
if (empty($properties->id)) {
if (empty($properties)) {
throw new coding_exception('You must provide a course request id when creating a course_request object');
}
$id = $properties;
$properties = new stdClass();
$properties->id = (int)$id;
unset($id);
}
if (empty($properties->requester)) {
if (!($this->properties = $DB->get_record('course_request', ['id' => $properties->id]))) {
throw new moodle_exception('unknowncourserequest');
}
} else {
$this->properties = $properties;
}
$this->properties->collision = null;
}
/**
* Returns the requested property
*
* @param string $key
* @return mixed
*/
public function __get($key) {
return $this->properties->$key;
}
/**
* Override this to ensure empty($request->blah) calls return a reliable answer...
*
* This is required because we define the __get method
*
* @param mixed $key
* @return bool True is it not empty, false otherwise
*/
public function __isset($key) {
return (!empty($this->properties->$key));
}
/**
* Returns the user who requested this course.
*
* Uses a static var to cache the results and cut down the number of db queries
*
* @return stdClass The user who requested the course
*/
public function get_requester() {
global $DB;
static $requesters = [];
if (!array_key_exists($this->properties->requester, $requesters)) {
$requesters[$this->properties->requester] = $DB->get_record('user', ['id' => $this->properties->requester]);
}
return $requesters[$this->properties->requester];
}
/**
* Checks that the shortname used by the course does not conflict with any other
* courses that exist
*
* @param string|null $shortnamemark The string to append to the requests shortname
* should a conflict be found
* @return bool true is there is a conflict, false otherwise
*/
public function check_shortname_collision($shortnamemark = '[*]') {
global $DB;
if ($this->properties->collision !== null) {
return $this->properties->collision;
}
if (empty($this->properties->shortname)) {
debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER);
$this->properties->collision = false;
} else if ($DB->record_exists('course', ['shortname' => $this->properties->shortname])) {
if (!empty($shortnamemark)) {
$this->properties->shortname .= ' ' . $shortnamemark;
}
$this->properties->collision = true;
} else {
$this->properties->collision = false;
}
return $this->properties->collision;
}
/**
* Checks user capability to approve a requested course
*
* If course was requested without category for some reason (might happen if $CFG->defaultrequestcategory is
* misconfigured), we check capabilities 'moodle/site:approvecourse' and 'moodle/course:changecategory'.
*
* @return bool
*/
public function can_approve() {
global $CFG;
$category = null;
if ($this->properties->category) {
$category = core_course_category::get($this->properties->category, IGNORE_MISSING);
} else if ($CFG->defaultrequestcategory) {
$category = core_course_category::get($CFG->defaultrequestcategory, IGNORE_MISSING);
}
if ($category) {
return has_capability('moodle/site:approvecourse', $category->get_context());
}
// We can not determine the context where the course should be created. The approver should have
// both capabilities to approve courses and change course category in the system context.
return has_all_capabilities(['moodle/site:approvecourse', 'moodle/course:changecategory'], context_system::instance());
}
/**
* Returns the category where this course request should be created
*
* Note that we don't check here that user has a capability to view
* hidden categories if he has capabilities 'moodle/site:approvecourse' and
* 'moodle/course:changecategory'
*
* @return core_course_category
*/
public function get_category() {
global $CFG;
if (
$this->properties->category
&& ($category = core_course_category::get($this->properties->category, IGNORE_MISSING))
) {
return $category;
} else if (
$CFG->defaultrequestcategory
&& ($category = core_course_category::get($CFG->defaultrequestcategory, IGNORE_MISSING))
) {
return $category;
} else {
return core_course_category::get_default();
}
}
/**
* This function approves the request turning it into a course
*
* This function converts the course request into a course, at the same time
* transferring any files used in the summary to the new course and then removing
* the course request and the files associated with it.
*
* @return int The id of the course that was created from this request
*/
public function approve() {
global $CFG, $DB, $USER;
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
$user = $DB->get_record('user', ['id' => $this->properties->requester, 'deleted' => 0], '*', MUST_EXIST);
$courseconfig = get_config('moodlecourse');
// Transfer appropriate settings.
$data = clone($this->properties);
unset($data->id);
unset($data->reason);
unset($data->requester);
// Set category.
$category = $this->get_category();
$data->category = $category->id;
// Set misc settings.
$data->requested = 1;
// Apply course default settings.
$data->format = $courseconfig->format;
$data->newsitems = $courseconfig->newsitems;
$data->showgrades = $courseconfig->showgrades;
$data->showreports = $courseconfig->showreports;
$data->maxbytes = $courseconfig->maxbytes;
$data->groupmode = $courseconfig->groupmode;
$data->groupmodeforce = $courseconfig->groupmodeforce;
$data->visible = $courseconfig->visible;
$data->visibleold = $data->visible;
$data->lang = $courseconfig->lang;
$data->enablecompletion = $courseconfig->enablecompletion;
$data->numsections = $courseconfig->numsections;
$data->startdate = usergetmidnight(time());
if ($courseconfig->courseenddateenabled) {
$data->enddate = usergetmidnight(time()) + $courseconfig->courseduration;
}
[$data->fullname, $data->shortname] = restore_dbops::calculate_course_names(0, $data->fullname, $data->shortname);
$course = create_course($data);
$context = context_course::instance($course->id, MUST_EXIST);
// Add enrol instances.
if (!$DB->record_exists('enrol', ['courseid' => $course->id, 'enrol' => 'manual'])) {
if ($manual = enrol_get_plugin('manual')) {
$manual->add_default_instance($course);
}
}
// Enrol the requester as teacher if necessary.
if (
!empty($CFG->creatornewroleid)
&& !is_viewing($context, $user, 'moodle/role:assign')
&& !is_enrolled($context, $user, 'moodle/role:assign')
) {
enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid);
}
$this->delete();
$a = new stdClass();
$a->name = format_string($course->fullname, true, ['context' => $context]);
$a->url = course_get_url($course);
$usernameplaceholders = \core\user::get_name_placeholders($user);
foreach ($usernameplaceholders as $field => $value) {
$a->{$field} = $value;
}
$this->notify(
touser: $user,
fromuser: $USER,
name: 'courserequestapproved',
subject: get_string('courseapprovedsubject'),
message: get_string('courseapprovedemail2', 'moodle', $a),
courseid: $course->id,
);
return $course->id;
}
/**
* Reject a course request
*
* This function rejects a course request, emailing the requesting user the
* provided notice and then removing the request from the database
*
* @param string $notice The message to display to the user
*/
public function reject($notice) {
global $USER, $DB;
$user = $DB->get_record('user', ['id' => $this->properties->requester], '*', MUST_EXIST);
$this->notify(
touser: $user,
fromuser: $USER,
name: 'courserequestrejected',
subject: get_string('courserejectsubject'),
message: get_string('courserejectemail', 'moodle', $notice),
);
$this->delete();
}
/**
* Deletes the course request and any associated files
*/
public function delete() {
global $DB;
$DB->delete_records('course_request', ['id' => $this->properties->id]);
}
/**
* Send a message from one user to another using events_trigger
*
* @param object $touser
* @param object $fromuser
* @param string $name
* @param string $subject
* @param string $message
* @param int|null $courseid
*/
protected function notify($touser, $fromuser, $name, $subject, $message, $courseid = null) {
$eventdata = new \core\message\message();
$eventdata->courseid = empty($courseid) ? SITEID : $courseid;
$eventdata->component = 'moodle';
$eventdata->name = $name;
$eventdata->userfrom = $fromuser;
$eventdata->userto = $touser;
$eventdata->subject = $subject;
$eventdata->fullmessage = $message;
$eventdata->fullmessageformat = FORMAT_PLAIN;
$eventdata->fullmessagehtml = '';
$eventdata->smallmessage = '';
$eventdata->notification = 1;
message_send($eventdata);
}
/**
* Checks if current user can request a course in this context
*
* @param context $context
* @return bool
*/
public static function can_request(context $context) {
global $CFG;
if (empty($CFG->enablecourserequests)) {
return false;
}
if (has_capability('moodle/course:create', $context)) {
return false;
}
if ($context instanceof context_system) {
$defaultcontext = context_coursecat::instance($CFG->defaultrequestcategory, IGNORE_MISSING);
return $defaultcontext && has_capability('moodle/course:request', $defaultcontext);
} else if ($context instanceof context_coursecat) {
if (!$CFG->lockrequestcategory || $CFG->defaultrequestcategory == $context->instanceid) {
return has_capability('moodle/course:request', $context);
}
}
return false;
}
}
// Alias this class to the old name.
// This file will be autoloaded by the legacyclasses autoload system.
// In future all uses of this class will be corrected and the legacy references will be removed.
class_alias(course_request::class, \course_request::class);
@@ -0,0 +1,340 @@
<?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/>.
namespace core_course;
use cm_info;
use core\context\course as context_course;
use core\exception\coding_exception;
use core\exception\moodle_exception;
use core_text;
use course_modinfo;
use navigation_cache;
use repository;
use stdClass;
/**
* Processes the upload, creating the course module and returning the result
*
* @package core
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dndupload_ajax_processor {
/** Returned when no error has occurred */
public const ERROR_OK = 0;
/** @var object The course that we are uploading to */
protected $course = null;
/** @var context_course The course context for capability checking */
protected $context = null;
/** @var int The section number we are uploading to */
protected $section = null;
/** @var string The type of upload (e.g. 'Files', 'text/plain') */
protected $type = null;
/** @var object The details of the module type that will be created */
protected $module = null;
/** @var object The course module that has been created */
protected $cm = null;
/** @var dndupload_handler used to check the allowed file types */
protected $dnduploadhandler = null;
/** @var string The name to give the new activity instance */
protected $displayname = null;
/**
* Set up some basic information needed to handle the upload
*
* @param int $courseid The ID of the course we are uploading to
* @param int $section The section number we are uploading to
* @param string $type The type of upload (as reported by the browser)
* @param string $modulename The name of the module requested to handle this upload
*/
public function __construct($courseid, $section, $type, $modulename) {
global $DB;
if (!defined('AJAX_SCRIPT')) {
throw new coding_exception('dndupload_ajax_processor should only be used within AJAX requests');
}
$this->course = $DB->get_record('course', ['id' => $courseid], '*', MUST_EXIST);
require_login($this->course, false);
$this->context = context_course::instance($this->course->id);
if (!is_number($section) || $section < 0) {
throw new coding_exception("Invalid section number $section");
}
$this->section = $section;
$this->type = $type;
if (!$this->module = $DB->get_record('modules', ['name' => $modulename])) {
throw new coding_exception("Module $modulename does not exist");
}
$this->dnduploadhandler = new dndupload_handler($this->course);
}
/**
* Check if this upload is a 'file' upload
*
* @return bool true if it is a 'file' upload, false otherwise
*/
protected function is_file_upload() {
return ($this->type == 'Files');
}
/**
* Process the upload - creating the module in the course and returning the result to the browser
*
* @param string $displayname optional the name (from the browser) to give the course module instance
* @param string $content optional the content of the upload (for non-file uploads)
*/
public function process($displayname = null, $content = null) {
require_capability('moodle/course:manageactivities', $this->context);
if ($this->is_file_upload()) {
require_capability('moodle/course:managefiles', $this->context);
if ($content != null) {
throw new moodle_exception('fileuploadwithcontent', 'moodle');
}
} else {
if (empty($content)) {
throw new moodle_exception('dnduploadwithoutcontent', 'moodle');
}
}
require_sesskey();
$this->displayname = $displayname;
if ($this->is_file_upload()) {
$this->handle_file_upload();
} else {
$this->handle_other_upload($content);
}
}
/**
* Handle uploads containing files - create the course module, ask the upload repository
* to process the file, ask the mod to set itself up, then return the result to the browser
*/
protected function handle_file_upload() {
global $CFG;
// Add the file to a draft file area.
$draftitemid = file_get_unused_draft_itemid();
$maxbytes = get_user_max_upload_file_size($this->context, $CFG->maxbytes, $this->course->maxbytes);
$types = $this->dnduploadhandler->get_handled_file_types($this->module->name);
$repo = repository::get_instances(['type' => 'upload', 'currentcontext' => $this->context]);
if (empty($repo)) {
throw new moodle_exception('errornouploadrepo', 'moodle');
}
$repo = reset($repo); // Get the first (and only) upload repo.
// Pre-emptively purge the navigation cache so the upload repo can close the session.
navigation_cache::destroy_volatile_caches();
$details = $repo->process_upload(null, $maxbytes, $types, '/', $draftitemid);
if (empty($this->displayname)) {
$this->displayname = $this->display_name_from_file($details['file']);
}
// Create a course module to hold the new instance.
$this->create_course_module();
// Ask the module to set itself up.
$moduledata = $this->prepare_module_data($draftitemid);
$instanceid = plugin_callback('mod', $this->module->name, 'dndupload', 'handle', [$moduledata], 'invalidfunction');
if ($instanceid === 'invalidfunction') {
throw new coding_exception(sprintf(
"%s does not support drag and drop upload (missing %s_dndupload_handle function",
$this->module->name,
$this->module->name,
));
}
// Finish setting up the course module.
$this->finish_setup_course_module($instanceid);
}
/**
* Handle uploads not containing file - create the course module, ask the mod to
* set itself up, then return the result to the browser
*
* @param string $content the content uploaded to the browser
*/
protected function handle_other_upload($content) {
// Check this plugin is registered to handle this type of upload.
if (!$this->dnduploadhandler->has_type_handler($this->module->name, $this->type)) {
$info = (object)['modname' => $this->module->name, 'type' => $this->type];
throw new moodle_exception('moddoesnotsupporttype', 'moodle', $info);
}
// Create a course module to hold the new instance.
$this->create_course_module();
// Ask the module to set itself up.
$moduledata = $this->prepare_module_data(null, $content);
$instanceid = plugin_callback('mod', $this->module->name, 'dndupload', 'handle', [$moduledata], 'invalidfunction');
if ($instanceid === 'invalidfunction') {
throw new coding_exception(sprintf(
"%s does not support drag and drop upload (missing %s_dndupload_handle function",
$this->module->name,
$this->module->name,
));
}
// Finish setting up the course module.
$this->finish_setup_course_module($instanceid);
}
/**
* Generate the name of the mod instance from the name of the file
* (remove the extension and convert underscore => space
*
* @param string $filename the filename of the uploaded file
* @return string the display name to use
*/
protected function display_name_from_file($filename) {
$pos = core_text::strrpos($filename, '.');
if ($pos) { // Want to skip if $pos === 0 OR $pos === false.
$filename = core_text::substr($filename, 0, $pos);
}
return str_replace('_', ' ', $filename);
}
/**
* Create the coursemodule to hold the file/content that has been uploaded
*/
protected function create_course_module() {
global $CFG;
require_once($CFG->dirroot . '/course/modlib.php');
[$module, $context, $cw, $cm, $data] = prepare_new_moduleinfo_data($this->course, $this->module->name, $this->section);
$data->coursemodule = $data->id = add_course_module($data);
$this->cm = $data;
}
/**
* Gather together all the details to pass on to the mod, so that it can initialise it's
* own database tables
*
* @param int $draftitemid optional the id of the draft area containing the file (for file uploads)
* @param string $content optional the content dropped onto the course (for non-file uploads)
* @return object data to pass on to the mod, containing:
* string $type the 'type' as registered with dndupload_handler (or 'Files')
* object $course the course the upload was for
* int $draftitemid optional the id of the draft area containing the files
* int $coursemodule id of the course module that has already been created
* string $displayname the name to use for this activity (can be overriden by the mod)
*/
protected function prepare_module_data($draftitemid = null, $content = null) {
$data = new stdClass();
$data->type = $this->type;
$data->course = $this->course;
if ($draftitemid) {
$data->draftitemid = $draftitemid;
} else if ($content) {
$data->content = $content;
}
$data->coursemodule = $this->cm->id;
$data->displayname = $this->displayname;
return $data;
}
/**
* Called after the mod has set itself up, to finish off any course module settings
* (set instance id, add to correct section, set visibility, etc.) and send the response
*
* @param int $instanceid id returned by the mod when it was created
*/
protected function finish_setup_course_module($instanceid) {
global $DB, $USER;
if (!$instanceid) {
// Something has gone wrong - undo everything we can.
course_delete_module($this->cm->id);
throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
}
// Note the section visibility.
$visible = get_fast_modinfo($this->course)->get_section_info($this->section)->visible;
$DB->set_field('course_modules', 'instance', $instanceid, ['id' => $this->cm->id]);
course_modinfo::purge_course_module_cache($this->course->id, $this->cm->id);
// Rebuild the course cache after update action.
rebuild_course_cache($this->course->id, true, true);
$sectionid = course_add_cm_to_section($this->course, $this->cm->id, $this->section, modname: $this->module->name);
set_coursemodule_visible($this->cm->id, $visible);
if (!$visible) {
$DB->set_field('course_modules', 'visibleold', 1, ['id' => $this->cm->id]);
}
// Retrieve the final info about this module.
$info = get_fast_modinfo($this->course);
if (!isset($info->cms[$this->cm->id])) {
// The course module has not been properly created in the course - undo everything.
course_delete_module($this->cm->id);
throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
}
$mod = $info->get_cm($this->cm->id);
// Trigger course module created event.
$event = \core\event\course_module_created::create_from_cm($mod);
$event->trigger();
$this->send_response($mod);
}
/**
* Send the details of the newly created activity back to the client browser
*
* @param cm_info $mod details of the mod just created
*/
protected function send_response($mod) {
global $OUTPUT, $PAGE;
$resp = new stdClass();
$resp->error = self::ERROR_OK;
$resp->elementid = 'module-' . $mod->id;
$resp->cmid = $mod->id;
$format = course_get_format($this->course);
$renderer = $format->get_renderer($PAGE);
$modinfo = $format->get_modinfo();
$section = $modinfo->get_section_info($mod->sectionnum);
// Get the new element html content.
$resp->fullcontent = $renderer->course_section_updated_cm_item($format, $section, $mod);
echo $OUTPUT->header();
echo json_encode($resp);
die();
}
}
// Alias this class to the old name.
// This file will be autoloaded by the legacyclasses autoload system.
// In future all uses of this class will be corrected and the legacy references will be removed.
class_alias(dndupload_ajax_processor::class, \dndupload_ajax_processor::class);
+342
View File
@@ -0,0 +1,342 @@
<?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/>.
namespace core_course;
use core\component;
use core\context\course as context_course;
use core\exception\coding_exception;
use repository;
use stdClass;
/**
* Stores all the information about the available dndupload handlers
*
* @package core
* @copyright 2012 Davo Smith
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dndupload_handler {
/**
* @var array A list of all registered mime types that can be dropped onto a course
* along with the modules that will handle them.
*/
protected $types = [];
/**
* @var array A list of the different file types (extensions) that different modules
* will handle.
*/
protected $filehandlers = [];
/**
* @var context_course|null
*/
protected $context = null;
/**
* Gather a list of dndupload handlers from the different mods
*
* @param object $course The course this is being added to (to check course_allowed_module() )
* @param array|null $modnames An array of module names that are allowed in this course.
*/
public function __construct($course, $modnames = null) {
global $CFG, $PAGE;
// Add some default types to handle.
// Note: 'Files' type is hard-coded into the Javascript as this needs to be ...
// ... treated a little differently.
$this->register_type(
identifier: 'url',
datatransfertypes: ['url', 'text/uri-list', 'text/x-moz-url'],
addmessage: get_string('addlinkhere', 'moodle'),
namemessage: get_string('nameforlink', 'moodle'),
handlermessage: get_string('whatforlink', 'moodle'),
priority: 10,
);
$this->register_type(
identifier: 'text/html',
datatransfertypes: ['text/html'],
addmessage: get_string('addpagehere', 'moodle'),
namemessage: get_string('nameforpage', 'moodle'),
handlermessage: get_string('whatforpage', 'moodle'),
priority: 20,
);
$this->register_type(
identifier: 'text',
datatransfertypes: ['text', 'text/plain'],
addmessage: get_string('addpagehere', 'moodle'),
namemessage: get_string('nameforpage', 'moodle'),
handlermessage: get_string('whatforpage', 'moodle'),
priority: 30,
);
$this->context = context_course::instance($course->id);
// Loop through all modules to find handlers.
$mods = get_plugin_list_with_function('mod', 'dndupload_register');
foreach ($mods as $component => $funcname) {
[$modtype, $modname] = component::normalize_component($component);
if ($modnames && !array_key_exists($modname, $modnames)) {
continue; // Module is deactivated (hidden) at the site level.
}
if (!course_allowed_module($course, $modname)) {
continue; // User does not have permission to add this module to the course.
}
$resp = $funcname();
if (!$resp) {
continue;
}
if (isset($resp['files'])) {
foreach ($resp['files'] as $file) {
$this->register_file_handler($file['extension'], $modname, $file['message']);
}
}
if (isset($resp['addtypes'])) {
foreach ($resp['addtypes'] as $type) {
if (isset($type['priority'])) {
$priority = $type['priority'];
} else {
$priority = 100;
}
if (!isset($type['handlermessage'])) {
$type['handlermessage'] = '';
}
$this->register_type(
identifier: $type['identifier'],
datatransfertypes: $type['datatransfertypes'],
addmessage: $type['addmessage'],
namemessage: $type['namemessage'],
handlermessage: $type['handlermessage'],
priority: $priority,
);
}
}
if (isset($resp['types'])) {
foreach ($resp['types'] as $type) {
$noname = !empty($type['noname']);
$this->register_type_handler($type['identifier'], $modname, $type['message'], $noname);
}
}
$PAGE->requires->string_for_js('pluginname', $modname);
}
}
/**
* Used to add a new mime type that can be drag and dropped onto a
* course displayed in a browser window
*
* @param string $identifier The name that this type will be known as
* @param array $datatransfertypes An array of the different types in the browser
* 'dataTransfer.types' object that will map to this type
* @param string $addmessage The message to display in the browser when this type is being
* dragged onto the page
* @param string $namemessage The message to pop up when asking for the name to give the
* course module instance when it is created
* @param string $handlermessage The message to pop up when asking which module should handle this type
* @param int $priority Controls the order in which types are checked by the browser (mainly
* needed to check for 'text' last as that is usually given as fallback)
*/
protected function register_type($identifier, $datatransfertypes, $addmessage, $namemessage, $handlermessage, $priority = 100) {
if ($this->is_known_type($identifier)) {
throw new coding_exception("Type $identifier is already registered");
}
$add = (object) [
'identifier' => $identifier,
'datatransfertypes' => $datatransfertypes,
'addmessage' => $addmessage,
'namemessage' => $namemessage,
'handlermessage' => $handlermessage,
'priority' => $priority,
'handlers' => [],
];
$this->types[$identifier] = $add;
}
/**
* Used to declare that a particular module will handle a particular type
* of dropped data
*
* @param string $type The name of the type (as declared in register_type)
* @param string $module The name of the module to handle this type
* @param string $message The message to show the user if more than one handler is registered
* for a type and the user needs to make a choice between them
* @param bool $noname If true, the 'name' dialog should be disabled in the pop-up.
* @throws coding_exception
*/
protected function register_type_handler($type, $module, $message, $noname) {
if (!$this->is_known_type($type)) {
throw new coding_exception("Trying to add handler for unknown type $type");
}
$add = (object) [
'type' => $type,
'module' => $module,
'message' => $message,
'noname' => $noname ? 1 : 0,
];
$this->types[$type]->handlers[] = $add;
}
/**
* Used to declare that a particular module will handle a particular type
* of dropped file
*
* @param string $extension The file extension to handle ('*' for all types)
* @param string $module The name of the module to handle this type
* @param string $message The message to show the user if more than one handler is registered
* for a type and the user needs to make a choice between them
*/
protected function register_file_handler($extension, $module, $message) {
$extension = strtolower($extension);
$add = (object) [
'extension' => $extension,
'module' => $module,
'message' => $message,
];
$this->filehandlers[] = $add;
}
/**
* Check to see if the type has been registered
*
* @param string $type The identifier of the type you are interested in
* @return bool True if the type is registered
*/
public function is_known_type($type) {
return array_key_exists($type, $this->types);
}
/**
* Check to see if the module in question has registered to handle the
* type given
*
* @param string $module The name of the module
* @param string $type The identifier of the type
* @return bool True if the module has registered to handle that type
*/
public function has_type_handler($module, $type) {
if (!$this->is_known_type($type)) {
throw new coding_exception("Checking for handler for unknown type $type");
}
foreach ($this->types[$type]->handlers as $handler) {
if ($handler->module == $module) {
return true;
}
}
return false;
}
/**
* Check to see if the module in question has registered to handle files
* with the given extension (or to handle all file types)
*
* @param string $module The name of the module
* @param string $extension The extension of the uploaded file
* @return bool True if the module has registered to handle files with
* that extension (or to handle all file types)
*/
public function has_file_handler($module, $extension) {
foreach ($this->filehandlers as $handler) {
if ($handler->module == $module) {
if ($handler->extension == '*' || $handler->extension == $extension) {
return true;
}
}
}
return false;
}
/**
* Gets a list of the file types that are handled by a particular module
*
* @param string $module The name of the module to check
* @return array of file extensions or string '*'
*/
public function get_handled_file_types($module) {
$types = [];
foreach ($this->filehandlers as $handler) {
if ($handler->module == $module) {
if ($handler->extension == '*') {
return '*';
} else {
// Prepending '.' as otherwise mimeinfo fails.
$types[] = '.' . $handler->extension;
}
}
}
return $types;
}
/**
* Returns an object to pass onto the javascript code with data about all the
* registered file / type handlers
*
* @return object Data to pass on to Javascript code
*/
public function get_js_data() {
global $CFG;
$ret = new stdClass();
// Sort the types by priority.
uasort($this->types, [$this, 'type_compare']);
$ret->types = [];
if (!empty($CFG->dndallowtextandlinks)) {
foreach ($this->types as $type) {
if (empty($type->handlers)) {
continue; // Skip any types without registered handlers.
}
$ret->types[] = $type;
}
}
$ret->filehandlers = $this->filehandlers;
$uploadrepo = repository::get_instances(['type' => 'upload', 'currentcontext' => $this->context]);
if (empty($uploadrepo)) {
$ret->filehandlers = []; // No upload repo => no file handlers.
}
return $ret;
}
/**
* Comparison function used when sorting types by priority
* @param object $type1 first type to compare
* @param object $type2 second type to compare
* @return integer -1 for $type1 < $type2; 1 for $type1 > $type2; 0 for equal
*/
protected function type_compare($type1, $type2) {
if ($type1->priority < $type2->priority) {
return -1;
}
if ($type1->priority > $type2->priority) {
return 1;
}
return 0;
}
}
// Alias this class to the old name.
// This file will be autoloaded by the legacyclasses autoload system.
// In future all uses of this class will be corrected and the legacy references will be removed.
class_alias(dndupload_handler::class, \dndupload_handler::class);
@@ -0,0 +1,53 @@
<?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/>.
namespace core_course\form;
use moodleform;
defined('MOODLE_INTERNAL') || die();
require_once("{$CFG->libdir}/formslib.php");
/**
* A form for an administrator to reject a course request.
*
* @package core_course
* @copyright Andrew Lyons <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reject_course_request extends moodleform {
#[\Override]
public function definition() {
$mform =& $this->_form;
$mform->addElement('hidden', 'reject', 0);
$mform->setType('reject', PARAM_INT);
$mform->addElement('header', 'coursedetails', get_string('coursereasonforrejecting'));
$mform->addElement(
'textarea',
'rejectnotice',
get_string('coursereasonforrejectingemail'),
['rows' => '15', 'cols' => '50'],
);
$mform->addRule('rejectnotice', get_string('missingreqreason'), 'required', null, 'client');
$mform->setType('rejectnotice', PARAM_TEXT);
$this->add_action_buttons(true, get_string('reject'));
}
}
@@ -0,0 +1,144 @@
<?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/>.
namespace core_course\form;
use core_course\course_request;
use core_course_category;
use moodleform;
defined('MOODLE_INTERNAL') || die();
require_once("{$CFG->libdir}/formslib.php");
/**
* A form for a user to request a course.
*
* @package core_course
* @copyright Andrew Lyons <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class request_course extends moodleform {
#[\Override]
public function definition() {
global $CFG, $DB, $USER;
$mform =& $this->_form;
if ($pending = $DB->get_records('course_request', ['requester' => $USER->id])) {
$mform->addElement('header', 'pendinglist', get_string('coursespending'));
$list = [];
foreach ($pending as $cp) {
$list[] = format_string($cp->fullname);
}
$list = implode(', ', $list);
$mform->addElement('static', 'pendingcourses', get_string('courses'), $list);
}
$mform->addElement('header', 'coursedetails', get_string('courserequestdetails'));
$mform->addElement(
'text',
'fullname',
get_string('fullnamecourse'),
['maxlength' => \core_course\constants::FULLNAME_MAXIMUM_LENGTH, 'size' => 50],
);
$mform->addHelpButton('fullname', 'fullnamecourse');
$mform->addRule('fullname', get_string('missingfullname'), 'required', null, 'client');
$mform->setType('fullname', PARAM_TEXT);
$mform->addElement(
'text',
'shortname',
get_string('shortnamecourse'),
['maxlength' => \core_course\constants::SHORTNAME_MAXIMUM_LENGTH, 'size' => 20],
);
$mform->addHelpButton('shortname', 'shortnamecourse');
$mform->addRule('shortname', get_string('missingshortname'), 'required', null, 'client');
$mform->setType('shortname', PARAM_TEXT);
if (empty($CFG->lockrequestcategory)) {
$displaylist = core_course_category::make_categories_list('moodle/course:request');
$mform->addElement('autocomplete', 'category', get_string('coursecategory'), $displaylist);
$mform->addRule('category', null, 'required', null, 'client');
$mform->setDefault('category', $CFG->defaultrequestcategory);
$mform->addHelpButton('category', 'coursecategory');
}
$mform->addElement(
'editor',
'summary_editor',
get_string('summary'),
null,
course_request::summary_editor_options(),
);
$mform->addHelpButton('summary_editor', 'coursesummary');
$mform->setType('summary_editor', PARAM_RAW);
$mform->addElement('header', 'requestreason', get_string('courserequestreason'));
$mform->addElement(
'textarea',
'reason',
get_string('courserequestsupport'),
['rows' => '15', 'cols' => '50'],
);
$mform->addRule('reason', get_string('missingreqreason'), 'required', null, 'client');
$mform->setType('reason', PARAM_TEXT);
$this->add_action_buttons(true, get_string('requestcourse'));
}
#[\Override]
public function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
$foundcourses = null;
$foundreqcourses = null;
if (!empty($data['shortname'])) {
$foundcourses = $DB->get_records('course', ['shortname' => $data['shortname']]);
$foundreqcourses = $DB->get_records('course_request', ['shortname' => $data['shortname']]);
}
if (!empty($foundreqcourses)) {
if (!empty($foundcourses)) {
$foundcourses = array_merge($foundcourses, $foundreqcourses);
} else {
$foundcourses = $foundreqcourses;
}
}
if (!empty($foundcourses)) {
foreach ($foundcourses as $foundcourse) {
if (!empty($foundcourse->requester)) {
$pending = 1;
$foundcoursenames[] = $foundcourse->fullname . ' [*]';
} else {
$foundcoursenames[] = $foundcourse->fullname;
}
}
$foundcoursenamestring = implode(',', $foundcoursenames);
$errors['shortname'] = get_string('shortnametaken', '', $foundcoursenamestring);
if (!empty($pending)) {
$errors['shortname'] .= get_string('starpending');
}
}
return $errors;
}
}
@@ -20,7 +20,7 @@ use action_menu;
use action_menu_link_secondary;
use context_coursecat;
use core_course_category;
use course_request;
use core_course\course_request;
use moodle_page;
use moodle_url;
+1 -2
View File
@@ -26,7 +26,6 @@
define('AJAX_SCRIPT', true);
require_once(__DIR__ . '/../config.php');
require_once($CFG->dirroot.'/course/dnduploadlib.php');
$courseid = required_param('course', PARAM_INT);
$section = required_param('section', PARAM_INT);
@@ -37,5 +36,5 @@ $contents = optional_param('contents', null, PARAM_RAW); // It will be up to eac
$PAGE->set_url('/course/dndupload.php');
$dndproc = new dndupload_ajax_processor($courseid, $section, $type, $modulename);
$dndproc = new \core_course\dndupload_ajax_processor($courseid, $section, $type, $modulename);
$dndproc->process($displayname, $contents);
-592
View File
@@ -25,8 +25,6 @@
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/repository/lib.php');
require_once($CFG->dirroot.'/repository/upload/lib.php');
require_once($CFG->dirroot.'/course/lib.php');
/**
@@ -90,593 +88,3 @@ function dndupload_add_to_course($course, $modnames) {
$PAGE->requires->js_init_call('M.course_dndupload.init', $vars, true, $jsmodule);
}
/**
* Stores all the information about the available dndupload handlers
*
* @package core
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dndupload_handler {
/**
* @var array A list of all registered mime types that can be dropped onto a course
* along with the modules that will handle them.
*/
protected $types = array();
/**
* @var array A list of the different file types (extensions) that different modules
* will handle.
*/
protected $filehandlers = array();
/**
* @var context_course|null
*/
protected $context = null;
/**
* Gather a list of dndupload handlers from the different mods
*
* @param object $course The course this is being added to (to check course_allowed_module() )
*/
public function __construct($course, $modnames = null) {
global $CFG, $PAGE;
// Add some default types to handle.
// Note: 'Files' type is hard-coded into the Javascript as this needs to be ...
// ... treated a little differently.
$this->register_type('url', array('url', 'text/uri-list', 'text/x-moz-url'), get_string('addlinkhere', 'moodle'),
get_string('nameforlink', 'moodle'), get_string('whatforlink', 'moodle'), 10);
$this->register_type('text/html', array('text/html'), get_string('addpagehere', 'moodle'),
get_string('nameforpage', 'moodle'), get_string('whatforpage', 'moodle'), 20);
$this->register_type('text', array('text', 'text/plain'), get_string('addpagehere', 'moodle'),
get_string('nameforpage', 'moodle'), get_string('whatforpage', 'moodle'), 30);
$this->context = context_course::instance($course->id);
// Loop through all modules to find handlers.
$mods = get_plugin_list_with_function('mod', 'dndupload_register');
foreach ($mods as $component => $funcname) {
list($modtype, $modname) = core_component::normalize_component($component);
if ($modnames && !array_key_exists($modname, $modnames)) {
continue; // Module is deactivated (hidden) at the site level.
}
if (!course_allowed_module($course, $modname)) {
continue; // User does not have permission to add this module to the course.
}
$resp = $funcname();
if (!$resp) {
continue;
}
if (isset($resp['files'])) {
foreach ($resp['files'] as $file) {
$this->register_file_handler($file['extension'], $modname, $file['message']);
}
}
if (isset($resp['addtypes'])) {
foreach ($resp['addtypes'] as $type) {
if (isset($type['priority'])) {
$priority = $type['priority'];
} else {
$priority = 100;
}
if (!isset($type['handlermessage'])) {
$type['handlermessage'] = '';
}
$this->register_type($type['identifier'], $type['datatransfertypes'],
$type['addmessage'], $type['namemessage'], $type['handlermessage'], $priority);
}
}
if (isset($resp['types'])) {
foreach ($resp['types'] as $type) {
$noname = !empty($type['noname']);
$this->register_type_handler($type['identifier'], $modname, $type['message'], $noname);
}
}
$PAGE->requires->string_for_js('pluginname', $modname);
}
}
/**
* Used to add a new mime type that can be drag and dropped onto a
* course displayed in a browser window
*
* @param string $identifier The name that this type will be known as
* @param array $datatransfertypes An array of the different types in the browser
* 'dataTransfer.types' object that will map to this type
* @param string $addmessage The message to display in the browser when this type is being
* dragged onto the page
* @param string $namemessage The message to pop up when asking for the name to give the
* course module instance when it is created
* @param string $handlermessage The message to pop up when asking which module should handle this type
* @param int $priority Controls the order in which types are checked by the browser (mainly
* needed to check for 'text' last as that is usually given as fallback)
*/
protected function register_type($identifier, $datatransfertypes, $addmessage, $namemessage, $handlermessage, $priority=100) {
if ($this->is_known_type($identifier)) {
throw new coding_exception("Type $identifier is already registered");
}
$add = new stdClass;
$add->identifier = $identifier;
$add->datatransfertypes = $datatransfertypes;
$add->addmessage = $addmessage;
$add->namemessage = $namemessage;
$add->handlermessage = $handlermessage;
$add->priority = $priority;
$add->handlers = array();
$this->types[$identifier] = $add;
}
/**
* Used to declare that a particular module will handle a particular type
* of dropped data
*
* @param string $type The name of the type (as declared in register_type)
* @param string $module The name of the module to handle this type
* @param string $message The message to show the user if more than one handler is registered
* for a type and the user needs to make a choice between them
* @param bool $noname If true, the 'name' dialog should be disabled in the pop-up.
* @throws coding_exception
*/
protected function register_type_handler($type, $module, $message, $noname) {
if (!$this->is_known_type($type)) {
throw new coding_exception("Trying to add handler for unknown type $type");
}
$add = new stdClass;
$add->type = $type;
$add->module = $module;
$add->message = $message;
$add->noname = $noname ? 1 : 0;
$this->types[$type]->handlers[] = $add;
}
/**
* Used to declare that a particular module will handle a particular type
* of dropped file
*
* @param string $extension The file extension to handle ('*' for all types)
* @param string $module The name of the module to handle this type
* @param string $message The message to show the user if more than one handler is registered
* for a type and the user needs to make a choice between them
*/
protected function register_file_handler($extension, $module, $message) {
$extension = strtolower($extension);
$add = new stdClass;
$add->extension = $extension;
$add->module = $module;
$add->message = $message;
$this->filehandlers[] = $add;
}
/**
* Check to see if the type has been registered
*
* @param string $type The identifier of the type you are interested in
* @return bool True if the type is registered
*/
public function is_known_type($type) {
return array_key_exists($type, $this->types);
}
/**
* Check to see if the module in question has registered to handle the
* type given
*
* @param string $module The name of the module
* @param string $type The identifier of the type
* @return bool True if the module has registered to handle that type
*/
public function has_type_handler($module, $type) {
if (!$this->is_known_type($type)) {
throw new coding_exception("Checking for handler for unknown type $type");
}
foreach ($this->types[$type]->handlers as $handler) {
if ($handler->module == $module) {
return true;
}
}
return false;
}
/**
* Check to see if the module in question has registered to handle files
* with the given extension (or to handle all file types)
*
* @param string $module The name of the module
* @param string $extension The extension of the uploaded file
* @return bool True if the module has registered to handle files with
* that extension (or to handle all file types)
*/
public function has_file_handler($module, $extension) {
foreach ($this->filehandlers as $handler) {
if ($handler->module == $module) {
if ($handler->extension == '*' || $handler->extension == $extension) {
return true;
}
}
}
return false;
}
/**
* Gets a list of the file types that are handled by a particular module
*
* @param string $module The name of the module to check
* @return array of file extensions or string '*'
*/
public function get_handled_file_types($module) {
$types = array();
foreach ($this->filehandlers as $handler) {
if ($handler->module == $module) {
if ($handler->extension == '*') {
return '*';
} else {
// Prepending '.' as otherwise mimeinfo fails.
$types[] = '.'.$handler->extension;
}
}
}
return $types;
}
/**
* Returns an object to pass onto the javascript code with data about all the
* registered file / type handlers
*
* @return object Data to pass on to Javascript code
*/
public function get_js_data() {
global $CFG;
$ret = new stdClass;
// Sort the types by priority.
uasort($this->types, array($this, 'type_compare'));
$ret->types = array();
if (!empty($CFG->dndallowtextandlinks)) {
foreach ($this->types as $type) {
if (empty($type->handlers)) {
continue; // Skip any types without registered handlers.
}
$ret->types[] = $type;
}
}
$ret->filehandlers = $this->filehandlers;
$uploadrepo = repository::get_instances(array('type' => 'upload', 'currentcontext' => $this->context));
if (empty($uploadrepo)) {
$ret->filehandlers = array(); // No upload repo => no file handlers.
}
return $ret;
}
/**
* Comparison function used when sorting types by priority
* @param object $type1 first type to compare
* @param object $type2 second type to compare
* @return integer -1 for $type1 < $type2; 1 for $type1 > $type2; 0 for equal
*/
protected function type_compare($type1, $type2) {
if ($type1->priority < $type2->priority) {
return -1;
}
if ($type1->priority > $type2->priority) {
return 1;
}
return 0;
}
}
/**
* Processes the upload, creating the course module and returning the result
*
* @package core
* @copyright 2012 Davo Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dndupload_ajax_processor {
/** Returned when no error has occurred */
const ERROR_OK = 0;
/** @var object The course that we are uploading to */
protected $course = null;
/** @var context_course The course context for capability checking */
protected $context = null;
/** @var int The section number we are uploading to */
protected $section = null;
/** @var string The type of upload (e.g. 'Files', 'text/plain') */
protected $type = null;
/** @var object The details of the module type that will be created */
protected $module= null;
/** @var object The course module that has been created */
protected $cm = null;
/** @var dndupload_handler used to check the allowed file types */
protected $dnduploadhandler = null;
/** @var string The name to give the new activity instance */
protected $displayname = null;
/**
* Set up some basic information needed to handle the upload
*
* @param int $courseid The ID of the course we are uploading to
* @param int $section The section number we are uploading to
* @param string $type The type of upload (as reported by the browser)
* @param string $modulename The name of the module requested to handle this upload
*/
public function __construct($courseid, $section, $type, $modulename) {
global $DB;
if (!defined('AJAX_SCRIPT')) {
throw new coding_exception('dndupload_ajax_processor should only be used within AJAX requests');
}
$this->course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
require_login($this->course, false);
$this->context = context_course::instance($this->course->id);
if (!is_number($section) || $section < 0) {
throw new coding_exception("Invalid section number $section");
}
$this->section = $section;
$this->type = $type;
if (!$this->module = $DB->get_record('modules', array('name' => $modulename))) {
throw new coding_exception("Module $modulename does not exist");
}
$this->dnduploadhandler = new dndupload_handler($this->course);
}
/**
* Check if this upload is a 'file' upload
*
* @return bool true if it is a 'file' upload, false otherwise
*/
protected function is_file_upload() {
return ($this->type == 'Files');
}
/**
* Process the upload - creating the module in the course and returning the result to the browser
*
* @param string $displayname optional the name (from the browser) to give the course module instance
* @param string $content optional the content of the upload (for non-file uploads)
*/
public function process($displayname = null, $content = null) {
require_capability('moodle/course:manageactivities', $this->context);
if ($this->is_file_upload()) {
require_capability('moodle/course:managefiles', $this->context);
if ($content != null) {
throw new moodle_exception('fileuploadwithcontent', 'moodle');
}
} else {
if (empty($content)) {
throw new moodle_exception('dnduploadwithoutcontent', 'moodle');
}
}
require_sesskey();
$this->displayname = $displayname;
if ($this->is_file_upload()) {
$this->handle_file_upload();
} else {
$this->handle_other_upload($content);
}
}
/**
* Handle uploads containing files - create the course module, ask the upload repository
* to process the file, ask the mod to set itself up, then return the result to the browser
*/
protected function handle_file_upload() {
global $CFG;
// Add the file to a draft file area.
$draftitemid = file_get_unused_draft_itemid();
$maxbytes = get_user_max_upload_file_size($this->context, $CFG->maxbytes, $this->course->maxbytes);
$types = $this->dnduploadhandler->get_handled_file_types($this->module->name);
$repo = repository::get_instances(array('type' => 'upload', 'currentcontext' => $this->context));
if (empty($repo)) {
throw new moodle_exception('errornouploadrepo', 'moodle');
}
$repo = reset($repo); // Get the first (and only) upload repo.
// Pre-emptively purge the navigation cache so the upload repo can close the session.
navigation_cache::destroy_volatile_caches();
$details = $repo->process_upload(null, $maxbytes, $types, '/', $draftitemid);
if (empty($this->displayname)) {
$this->displayname = $this->display_name_from_file($details['file']);
}
// Create a course module to hold the new instance.
$this->create_course_module();
// Ask the module to set itself up.
$moduledata = $this->prepare_module_data($draftitemid);
$instanceid = plugin_callback('mod', $this->module->name, 'dndupload', 'handle', array($moduledata), 'invalidfunction');
if ($instanceid === 'invalidfunction') {
throw new coding_exception("{$this->module->name} does not support drag and drop upload (missing {$this->module->name}_dndupload_handle function");
}
// Finish setting up the course module.
$this->finish_setup_course_module($instanceid);
}
/**
* Handle uploads not containing file - create the course module, ask the mod to
* set itself up, then return the result to the browser
*
* @param string $content the content uploaded to the browser
*/
protected function handle_other_upload($content) {
// Check this plugin is registered to handle this type of upload
if (!$this->dnduploadhandler->has_type_handler($this->module->name, $this->type)) {
$info = (object)array('modname' => $this->module->name, 'type' => $this->type);
throw new moodle_exception('moddoesnotsupporttype', 'moodle', $info);
}
// Create a course module to hold the new instance.
$this->create_course_module();
// Ask the module to set itself up.
$moduledata = $this->prepare_module_data(null, $content);
$instanceid = plugin_callback('mod', $this->module->name, 'dndupload', 'handle', array($moduledata), 'invalidfunction');
if ($instanceid === 'invalidfunction') {
throw new coding_exception("{$this->module->name} does not support drag and drop upload (missing {$this->module->name}_dndupload_handle function");
}
// Finish setting up the course module.
$this->finish_setup_course_module($instanceid);
}
/**
* Generate the name of the mod instance from the name of the file
* (remove the extension and convert underscore => space
*
* @param string $filename the filename of the uploaded file
* @return string the display name to use
*/
protected function display_name_from_file($filename) {
$pos = core_text::strrpos($filename, '.');
if ($pos) { // Want to skip if $pos === 0 OR $pos === false.
$filename = core_text::substr($filename, 0, $pos);
}
return str_replace('_', ' ', $filename);
}
/**
* Create the coursemodule to hold the file/content that has been uploaded
*/
protected function create_course_module() {
global $CFG;
require_once($CFG->dirroot.'/course/modlib.php');
list($module, $context, $cw, $cm, $data) = prepare_new_moduleinfo_data($this->course, $this->module->name, $this->section);
$data->coursemodule = $data->id = add_course_module($data);
$this->cm = $data;
}
/**
* Gather together all the details to pass on to the mod, so that it can initialise it's
* own database tables
*
* @param int $draftitemid optional the id of the draft area containing the file (for file uploads)
* @param string $content optional the content dropped onto the course (for non-file uploads)
* @return object data to pass on to the mod, containing:
* string $type the 'type' as registered with dndupload_handler (or 'Files')
* object $course the course the upload was for
* int $draftitemid optional the id of the draft area containing the files
* int $coursemodule id of the course module that has already been created
* string $displayname the name to use for this activity (can be overriden by the mod)
*/
protected function prepare_module_data($draftitemid = null, $content = null) {
$data = new stdClass();
$data->type = $this->type;
$data->course = $this->course;
if ($draftitemid) {
$data->draftitemid = $draftitemid;
} else if ($content) {
$data->content = $content;
}
$data->coursemodule = $this->cm->id;
$data->displayname = $this->displayname;
return $data;
}
/**
* Called after the mod has set itself up, to finish off any course module settings
* (set instance id, add to correct section, set visibility, etc.) and send the response
*
* @param int $instanceid id returned by the mod when it was created
*/
protected function finish_setup_course_module($instanceid) {
global $DB, $USER;
if (!$instanceid) {
// Something has gone wrong - undo everything we can.
course_delete_module($this->cm->id);
throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
}
// Note the section visibility
$visible = get_fast_modinfo($this->course)->get_section_info($this->section)->visible;
$DB->set_field('course_modules', 'instance', $instanceid, array('id' => $this->cm->id));
\course_modinfo::purge_course_module_cache($this->course->id, $this->cm->id);
// Rebuild the course cache after update action
rebuild_course_cache($this->course->id, true, true);
$sectionid = course_add_cm_to_section($this->course, $this->cm->id, $this->section, modname: $this->module->name);
set_coursemodule_visible($this->cm->id, $visible);
if (!$visible) {
$DB->set_field('course_modules', 'visibleold', 1, array('id' => $this->cm->id));
}
// retrieve the final info about this module.
$info = get_fast_modinfo($this->course);
if (!isset($info->cms[$this->cm->id])) {
// The course module has not been properly created in the course - undo everything.
course_delete_module($this->cm->id);
throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
}
$mod = $info->get_cm($this->cm->id);
// Trigger course module created event.
$event = \core\event\course_module_created::create_from_cm($mod);
$event->trigger();
$this->send_response($mod);
}
/**
* Send the details of the newly created activity back to the client browser
*
* @param cm_info $mod details of the mod just created
*/
protected function send_response($mod) {
global $OUTPUT, $PAGE;
$resp = new stdClass();
$resp->error = self::ERROR_OK;
$resp->elementid = 'module-' . $mod->id;
$resp->cmid = $mod->id;
$format = course_get_format($this->course);
$renderer = $format->get_renderer($PAGE);
$modinfo = $format->get_modinfo();
$section = $modinfo->get_section_info($mod->sectionnum);
// Get the new element html content.
$resp->fullcontent = $renderer->course_section_updated_cm_item($format, $section, $mod);
echo $OUTPUT->header();
echo json_encode($resp);
die();
}
}
+1 -6
View File
@@ -16,17 +16,12 @@
namespace core_courseformat\external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/course/dnduploadlib.php');
use core_course\dndupload_handler;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use dndupload_handler;
/**
* Class for exporting a course file handlers.
+1 -8
View File
@@ -16,15 +16,9 @@
namespace core_courseformat\external;
use core_course\dndupload_handler;
use core_external\external_api;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
use dndupload_handler;
/**
* Tests for the file_hanlders class.
*
@@ -42,7 +36,6 @@ final class file_handlers_test extends \externallib_advanced_testcase {
public static function setupBeforeClass(): void { // phpcs:ignore
global $CFG;
require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . '/course/dnduploadlib.php');
}
/**
-419
View File
@@ -26,7 +26,6 @@ defined('MOODLE_INTERNAL') || die;
use core\di;
use core\hook;
use core_course\external\course_summary_exporter;
use core_courseformat\base as course_format;
use core_courseformat\formatactions;
use core_courseformat\sectiondelegate;
@@ -2333,424 +2332,6 @@ function average_number_of_courses_modules() {
return $coursemoduleaverage;
}
/**
* This class pertains to course requests and contains methods associated with
* create, approving, and removing course requests.
*
* Please note we do not allow embedded images here because there is no context
* to store them with proper access control.
*
* @copyright 2009 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.0
*
* @property-read int $id
* @property-read string $fullname
* @property-read string $shortname
* @property-read string $summary
* @property-read int $summaryformat
* @property-read int $summarytrust
* @property-read string $reason
* @property-read int $requester
*/
class course_request {
/**
* This is the stdClass that stores the properties for the course request
* and is externally accessed through the __get magic method
* @var stdClass
*/
protected $properties;
/**
* An array of options for the summary editor used by course request forms.
* This is initially set by {@link summary_editor_options()}
* @var array
* @static
*/
protected static $summaryeditoroptions;
/**
* Static function to prepare the summary editor for working with a course
* request.
*
* @static
* @param null|stdClass $data Optional, an object containing the default values
* for the form, these may be modified when preparing the
* editor so this should be called before creating the form
* @return stdClass An object that can be used to set the default values for
* an mforms form
*/
public static function prepare($data=null) {
if ($data === null) {
$data = new stdClass;
}
$data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options());
return $data;
}
/**
* Static function to create a new course request when passed an array of properties
* for it.
*
* This function also handles saving any files that may have been used in the editor
*
* @static
* @param stdClass $data
* @return course_request The newly created course request
*/
public static function create($data) {
global $USER, $DB, $CFG;
$data->requester = $USER->id;
// Setting the default category if none set.
if (empty($data->category) || !empty($CFG->lockrequestcategory)) {
$data->category = $CFG->defaultrequestcategory;
}
// Summary is a required field so copy the text over
$data->summary = $data->summary_editor['text'];
$data->summaryformat = $data->summary_editor['format'];
$data->id = $DB->insert_record('course_request', $data);
// Create a new course_request object and return it
$request = new course_request($data);
// Notify the admin if required.
if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) {
$a = new stdClass;
$a->link = "$CFG->wwwroot/course/pending.php";
$a->user = fullname($USER);
$a->shortname = s($data->shortname) ?? '';
$a->fullname = s($data->fullname) ?? '';
$a->category = s($data->category) ?? '';
$a->reason = format_text($data->reason, FORMAT_PLAIN) ?? '';
$subject = get_string('courserequest');
$message = get_string('courserequestnotifyemail', 'admin', $a);
foreach ($users as $user) {
$request->notify($user, $USER, 'courserequested', $subject, $message);
}
}
return $request;
}
/**
* Returns an array of options to use with a summary editor
*
* @uses course_request::$summaryeditoroptions
* @return array An array of options to use with the editor
*/
public static function summary_editor_options() {
global $CFG;
if (self::$summaryeditoroptions === null) {
self::$summaryeditoroptions = array('maxfiles' => 0, 'maxbytes'=>0);
}
return self::$summaryeditoroptions;
}
/**
* Loads the properties for this course request object. Id is required and if
* only id is provided then we load the rest of the properties from the database
*
* @param stdClass|int $properties Either an object containing properties
* or the course_request id to load
*/
public function __construct($properties) {
global $DB;
if (empty($properties->id)) {
if (empty($properties)) {
throw new coding_exception('You must provide a course request id when creating a course_request object');
}
$id = $properties;
$properties = new stdClass;
$properties->id = (int)$id;
unset($id);
}
if (empty($properties->requester)) {
if (!($this->properties = $DB->get_record('course_request', array('id' => $properties->id)))) {
throw new \moodle_exception('unknowncourserequest');
}
} else {
$this->properties = $properties;
}
$this->properties->collision = null;
}
/**
* Returns the requested property
*
* @param string $key
* @return mixed
*/
public function __get($key) {
return $this->properties->$key;
}
/**
* Override this to ensure empty($request->blah) calls return a reliable answer...
*
* This is required because we define the __get method
*
* @param mixed $key
* @return bool True is it not empty, false otherwise
*/
public function __isset($key) {
return (!empty($this->properties->$key));
}
/**
* Returns the user who requested this course
*
* Uses a static var to cache the results and cut down the number of db queries
*
* @staticvar array $requesters An array of cached users
* @return stdClass The user who requested the course
*/
public function get_requester() {
global $DB;
static $requesters= array();
if (!array_key_exists($this->properties->requester, $requesters)) {
$requesters[$this->properties->requester] = $DB->get_record('user', array('id'=>$this->properties->requester));
}
return $requesters[$this->properties->requester];
}
/**
* Checks that the shortname used by the course does not conflict with any other
* courses that exist
*
* @param string|null $shortnamemark The string to append to the requests shortname
* should a conflict be found
* @return bool true is there is a conflict, false otherwise
*/
public function check_shortname_collision($shortnamemark = '[*]') {
global $DB;
if ($this->properties->collision !== null) {
return $this->properties->collision;
}
if (empty($this->properties->shortname)) {
debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER);
$this->properties->collision = false;
} else if ($DB->record_exists('course', array('shortname' => $this->properties->shortname))) {
if (!empty($shortnamemark)) {
$this->properties->shortname .= ' '.$shortnamemark;
}
$this->properties->collision = true;
} else {
$this->properties->collision = false;
}
return $this->properties->collision;
}
/**
* Checks user capability to approve a requested course
*
* If course was requested without category for some reason (might happen if $CFG->defaultrequestcategory is
* misconfigured), we check capabilities 'moodle/site:approvecourse' and 'moodle/course:changecategory'.
*
* @return bool
*/
public function can_approve() {
global $CFG;
$category = null;
if ($this->properties->category) {
$category = core_course_category::get($this->properties->category, IGNORE_MISSING);
} else if ($CFG->defaultrequestcategory) {
$category = core_course_category::get($CFG->defaultrequestcategory, IGNORE_MISSING);
}
if ($category) {
return has_capability('moodle/site:approvecourse', $category->get_context());
}
// We can not determine the context where the course should be created. The approver should have
// both capabilities to approve courses and change course category in the system context.
return has_all_capabilities(['moodle/site:approvecourse', 'moodle/course:changecategory'], context_system::instance());
}
/**
* Returns the category where this course request should be created
*
* Note that we don't check here that user has a capability to view
* hidden categories if he has capabilities 'moodle/site:approvecourse' and
* 'moodle/course:changecategory'
*
* @return core_course_category
*/
public function get_category() {
global $CFG;
if ($this->properties->category && ($category = core_course_category::get($this->properties->category, IGNORE_MISSING))) {
return $category;
} else if ($CFG->defaultrequestcategory &&
($category = core_course_category::get($CFG->defaultrequestcategory, IGNORE_MISSING))) {
return $category;
} else {
return core_course_category::get_default();
}
}
/**
* This function approves the request turning it into a course
*
* This function converts the course request into a course, at the same time
* transferring any files used in the summary to the new course and then removing
* the course request and the files associated with it.
*
* @return int The id of the course that was created from this request
*/
public function approve() {
global $CFG, $DB, $USER;
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
$user = $DB->get_record('user', array('id' => $this->properties->requester, 'deleted'=>0), '*', MUST_EXIST);
$courseconfig = get_config('moodlecourse');
// Transfer appropriate settings
$data = clone($this->properties);
unset($data->id);
unset($data->reason);
unset($data->requester);
// Set category
$category = $this->get_category();
$data->category = $category->id;
// Set misc settings
$data->requested = 1;
// Apply course default settings
$data->format = $courseconfig->format;
$data->newsitems = $courseconfig->newsitems;
$data->showgrades = $courseconfig->showgrades;
$data->showreports = $courseconfig->showreports;
$data->maxbytes = $courseconfig->maxbytes;
$data->groupmode = $courseconfig->groupmode;
$data->groupmodeforce = $courseconfig->groupmodeforce;
$data->visible = $courseconfig->visible;
$data->visibleold = $data->visible;
$data->lang = $courseconfig->lang;
$data->enablecompletion = $courseconfig->enablecompletion;
$data->numsections = $courseconfig->numsections;
$data->startdate = usergetmidnight(time());
if ($courseconfig->courseenddateenabled) {
$data->enddate = usergetmidnight(time()) + $courseconfig->courseduration;
}
list($data->fullname, $data->shortname) = restore_dbops::calculate_course_names(0, $data->fullname, $data->shortname);
$course = create_course($data);
$context = context_course::instance($course->id, MUST_EXIST);
// add enrol instances
if (!$DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) {
if ($manual = enrol_get_plugin('manual')) {
$manual->add_default_instance($course);
}
}
// enrol the requester as teacher if necessary
if (!empty($CFG->creatornewroleid) and !is_viewing($context, $user, 'moodle/role:assign') and !is_enrolled($context, $user, 'moodle/role:assign')) {
enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid);
}
$this->delete();
$a = new stdClass();
$a->name = format_string($course->fullname, true, ['context' => $context]);
$a->url = course_get_url($course);
$usernameplaceholders = \core\user::get_name_placeholders($user);
foreach ($usernameplaceholders as $field => $value) {
$a->{$field} = $value;
}
$this->notify($user, $USER, 'courserequestapproved', get_string('courseapprovedsubject'), get_string('courseapprovedemail2', 'moodle', $a), $course->id);
return $course->id;
}
/**
* Reject a course request
*
* This function rejects a course request, emailing the requesting user the
* provided notice and then removing the request from the database
*
* @param string $notice The message to display to the user
*/
public function reject($notice) {
global $USER, $DB;
$user = $DB->get_record('user', array('id' => $this->properties->requester), '*', MUST_EXIST);
$this->notify($user, $USER, 'courserequestrejected', get_string('courserejectsubject'), get_string('courserejectemail', 'moodle', $notice));
$this->delete();
}
/**
* Deletes the course request and any associated files
*/
public function delete() {
global $DB;
$DB->delete_records('course_request', array('id' => $this->properties->id));
}
/**
* Send a message from one user to another using events_trigger
*
* @param object $touser
* @param object $fromuser
* @param string $name
* @param string $subject
* @param string $message
* @param int|null $courseid
*/
protected function notify($touser, $fromuser, $name, $subject, $message, $courseid = null) {
$eventdata = new \core\message\message();
$eventdata->courseid = empty($courseid) ? SITEID : $courseid;
$eventdata->component = 'moodle';
$eventdata->name = $name;
$eventdata->userfrom = $fromuser;
$eventdata->userto = $touser;
$eventdata->subject = $subject;
$eventdata->fullmessage = $message;
$eventdata->fullmessageformat = FORMAT_PLAIN;
$eventdata->fullmessagehtml = '';
$eventdata->smallmessage = '';
$eventdata->notification = 1;
message_send($eventdata);
}
/**
* Checks if current user can request a course in this context
*
* @param context $context
* @return bool
*/
public static function can_request(context $context) {
global $CFG;
if (empty($CFG->enablecourserequests)) {
return false;
}
if (has_capability('moodle/course:create', $context)) {
return false;
}
if ($context instanceof context_system) {
$defaultcontext = context_coursecat::instance($CFG->defaultrequestcategory, IGNORE_MISSING);
return $defaultcontext &&
has_capability('moodle/course:request', $defaultcontext);
} else if ($context instanceof context_coursecat) {
if (!$CFG->lockrequestcategory || $CFG->defaultrequestcategory == $context->instanceid) {
return has_capability('moodle/course:request', $context);
}
}
return false;
}
}
/**
* Return a list of page types
* @param string $pagetype current page type
+77 -64
View File
@@ -1,39 +1,37 @@
<?php
///////////////////////////////////////////////////////////////////////////
// //
// NOTICE OF COPYRIGHT //
// //
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
// http://moodle.org //
// //
// Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
// //
// This program 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 2 of the License, or //
// (at your option) any later version. //
// //
// This program 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: //
// //
// http://www.gnu.org/copyleft/gpl.html //
// //
///////////////////////////////////////////////////////////////////////////
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
/**
* Allow the administrator to look through a list of course requests and approve or reject them.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package course
* @copyright Moodle Pty Ltd
*/
use core\context\course as context_course;
use core\context\system as context_system;
use core\exception\moodle_exception;
use core\url;
use core\output\html_writer;
use core_course\form\reject_course_request as reject_course_request_form;
require_once(__DIR__ . '/../config.php');
require_once($CFG->libdir . '/adminlib.php');
require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . '/course/request_form.php');
$approve = optional_param('approve', 0, PARAM_INT);
$reject = optional_param('reject', 0, PARAM_INT);
@@ -51,58 +49,56 @@ if (has_capability('moodle/site:approvecourse', $context)) {
require_capability('moodle/site:approvecourse', $context);
}
$PAGE->set_context($context);
$PAGE->set_url(new moodle_url('/course/pending.php'));
$PAGE->set_url(new url('/course/pending.php'));
}
/// Process approval of a course.
if (!empty($approve) and confirm_sesskey()) {
/// Load the request.
// Process approval of a course.
if (!empty($approve) && confirm_sesskey()) {
// Load the request.
$course = new course_request($approve);
$courseid = $course->approve();
if ($courseid !== false) {
if (has_capability('moodle/course:update', context_course::instance($courseid))) {
redirect(new moodle_url('/course/edit.php', ['id' => $courseid, 'returnto' => 'pending']));
redirect(new url('/course/edit.php', ['id' => $courseid, 'returnto' => 'pending']));
} else {
redirect(new moodle_url('/course/view.php', ['id' => $courseid]));
redirect(new url('/course/view.php', ['id' => $courseid]));
}
} else {
throw new \moodle_exception('courseapprovedfailed');
throw new moodle_exception('courseapprovedfailed');
}
}
/// Process rejection of a course.
// Process rejection of a course.
if (!empty($reject)) {
// Load the request.
$course = new course_request($reject);
// Prepare the form.
$rejectform = new reject_request_form($baseurl);
$rejectform = new reject_course_request_form($baseurl);
$default = new stdClass();
$default->reject = $course->id;
$rejectform->set_data($default);
/// Standard form processing if statement.
if ($rejectform->is_cancelled()){
// Standard form processing if statement.
if ($rejectform->is_cancelled()) {
redirect($baseurl);
} else if ($data = $rejectform->get_data()) {
/// Reject the request
// Reject the request.
$course->reject($data->rejectnotice);
/// Redirect back to the course listing.
// Redirect back to the course listing.
redirect($baseurl, get_string('courserejected'));
}
/// Display the form for giving a reason for rejecting the request.
// Display the form for giving a reason for rejecting the request.
echo $OUTPUT->header($rejectform->focus());
$rejectform->display();
echo $OUTPUT->footer();
exit;
}
/// Print a list of all the pending requests.
// Print a list of all the pending requests.
echo $OUTPUT->header();
$pending = $DB->get_records('course_request');
@@ -115,16 +111,16 @@ if (empty($pending)) {
if ($role) {
echo $OUTPUT->notification(get_string('courserequestwarning', 'core', role_get_name($role)), 'notifyproblem');
} else {
$userpoliciesurl = new moodle_url('/admin/settings.php', ['section' => 'userpolicies']);
$userpoliciesurl = new url('/admin/settings.php', ['section' => 'userpolicies']);
echo $OUTPUT->notification(get_string('courserequestroleerror', 'core', (string) $userpoliciesurl), 'notifyerror');
}
/// Build a table of all the requests.
// Build a table of all the requests.
$table = new html_table();
$table->attributes['class'] = 'pendingcourserequests table generaltable';
$table->align = array('center', 'center', 'center', 'center', 'center', 'center');
$table->head = array(get_string('requestedby'), get_string('shortnamecourse'), get_string('fullnamecourse'),
get_string('summary'), get_string('category'), get_string('requestreason'), get_string('action'));
$table->align = ['center', 'center', 'center', 'center', 'center', 'center'];
$table->head = [get_string('requestedby'), get_string('shortnamecourse'), get_string('fullnamecourse'),
get_string('summary'), get_string('category'), get_string('requestreason'), get_string('action')];
foreach ($pending as $course) {
$course = new course_request($course);
@@ -138,34 +134,51 @@ if (empty($pending)) {
$category = $course->get_category();
// Fullname of the user who requested the course (with link to profile if current user can view it).
$requesterfullname = $OUTPUT->user_picture($course->get_requester(), [
'includefullname' => true,
'link' => user_can_view_profile($course->get_requester()),
]);
$requesterfullname = $OUTPUT->user_picture(
$course->get_requester(),
[
'includefullname' => true,
'link' => user_can_view_profile($course->get_requester()),
],
);
$row = array();
$row[] = $requesterfullname;
$row[] = format_string($course->shortname);
$row[] = format_string($course->fullname);
$row[] = format_text($course->summary, $course->summaryformat);
$row[] = $category->get_formatted_name();
$row[] = format_string($course->reason);
$row[] = $OUTPUT->single_button(new moodle_url($baseurl, array('approve' => $course->id, 'sesskey' => sesskey())), get_string('approve'), 'get') .
$OUTPUT->single_button(new moodle_url($baseurl, array('reject' => $course->id)), get_string('rejectdots'), 'get');
$row = [
$requesterfullname,
format_string($course->shortname),
format_string($course->fullname),
format_text($course->summary, $course->summaryformat),
$category->get_formatted_name(),
format_string($course->reason),
];
/// Add the row to the table.
$controls = [
$OUTPUT->single_button(
new url($baseurl, ['approve' => $course->id, 'sesskey' => sesskey()]),
get_string('approve'),
'get',
),
$OUTPUT->single_button(
new url($baseurl, ['reject' => $course->id]),
get_string('rejectdots'),
'get',
),
];
$row[] = implode(' ', $controls);
// Add the row to the table.
$table->data[] = $row;
}
/// Display the table.
// Display the table.
echo html_writer::table($table);
/// Message about name collisions, if necessary.
// Message about name collisions, if necessary.
if (!empty($collision)) {
print_string('shortnamecollisionwarning');
}
}
/// Finish off the page.
// Finish off the page.
echo $OUTPUT->single_button($CFG->wwwroot . '/course/index.php', get_string('backtocourselisting'));
echo $OUTPUT->footer();
+14 -11
View File
@@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -15,6 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
use core\context\coursecat as context_coursecat;
use core\exception\moodle_exception;
use core\url;
use core_course\course_request;
use core_course\form\request_course as request_course_form;
/**
* Allows a user to request a course be created for them.
*
@@ -25,17 +30,16 @@
require_once(__DIR__ . '/../config.php');
require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . '/course/request_form.php');
// Where we came from. Used in a number of redirects.
$url = new moodle_url('/course/request.php');
$url = new url('/course/request.php');
$return = optional_param('return', null, PARAM_ALPHANUMEXT);
$categoryid = optional_param('category', null, PARAM_INT);
if ($return === 'management') {
$url->param('return', $return);
$returnurl = new moodle_url('/course/management.php', array('categoryid' => $CFG->defaultrequestcategory));
$returnurl = new url('/course/management.php', ['categoryid' => $CFG->defaultrequestcategory]);
} else {
$returnurl = new moodle_url('/course/index.php');
$returnurl = new url('/course/index.php');
}
$PAGE->set_url($url);
@@ -43,10 +47,10 @@ $PAGE->set_url($url);
// Check permissions.
require_login(null, false);
if (isguestuser()) {
throw new \moodle_exception('guestsarenotallowed', '', $returnurl);
throw new moodle_exception('guestsarenotallowed', '', $returnurl);
}
if (empty($CFG->enablecourserequests)) {
throw new \moodle_exception('courserequestdisabled', '', $returnurl);
throw new moodle_exception('courserequestdisabled', '', $returnurl);
}
if ($CFG->lockrequestcategory) {
@@ -66,7 +70,7 @@ require_capability('moodle/course:request', $context);
// Set up the form.
$data = $categoryid ? (object)['category' => $categoryid] : null;
$data = course_request::prepare($data);
$requestform = new course_request_form($url);
$requestform = new request_course_form($url);
$requestform->set_data($data);
$strtitle = get_string('courserequest');
@@ -76,9 +80,8 @@ $PAGE->set_heading($coursecategory->get_formatted_name());
$PAGE->set_primary_active_tab('home');
// Standard form processing if statement.
if ($requestform->is_cancelled()){
if ($requestform->is_cancelled()) {
redirect($returnurl);
} else if ($data = $requestform->get_data()) {
$request = course_request::create($data);
@@ -86,7 +89,7 @@ if ($requestform->is_cancelled()){
notice(get_string('courserequestsuccess'), $returnurl);
}
$categoryurl = new moodle_url('/course/index.php');
$categoryurl = new url('/course/index.php');
if ($categoryid) {
$categoryurl->param('categoryid', $categoryid);
}
-153
View File
@@ -1,153 +0,0 @@
<?php
///////////////////////////////////////////////////////////////////////////
// //
// NOTICE OF COPYRIGHT //
// //
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
// http://moodle.org //
// //
// Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
// //
// This program 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 2 of the License, or //
// (at your option) any later version. //
// //
// This program 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: //
// //
// http://www.gnu.org/copyleft/gpl.html //
// //
///////////////////////////////////////////////////////////////////////////
/**
* Forms associated with requesting courses, and having requests approved.
* Note that several related forms are defined in this one file.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package course
*/
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
}
require_once($CFG->libdir.'/formslib.php');
/**
* A form for a user to request a course.
*/
class course_request_form extends moodleform {
function definition() {
global $CFG, $DB, $USER;
$mform =& $this->_form;
if ($pending = $DB->get_records('course_request', array('requester' => $USER->id))) {
$mform->addElement('header', 'pendinglist', get_string('coursespending'));
$list = array();
foreach ($pending as $cp) {
$list[] = format_string($cp->fullname);
}
$list = implode(', ', $list);
$mform->addElement('static', 'pendingcourses', get_string('courses'), $list);
}
$mform->addElement('header','coursedetails', get_string('courserequestdetails'));
$mform->addElement('text', 'fullname', get_string('fullnamecourse'),
['maxlength' => \core_course\constants::FULLNAME_MAXIMUM_LENGTH, 'size' => 50]);
$mform->addHelpButton('fullname', 'fullnamecourse');
$mform->addRule('fullname', get_string('missingfullname'), 'required', null, 'client');
$mform->setType('fullname', PARAM_TEXT);
$mform->addElement('text', 'shortname', get_string('shortnamecourse'),
['maxlength' => \core_course\constants::SHORTNAME_MAXIMUM_LENGTH, 'size' => 20]);
$mform->addHelpButton('shortname', 'shortnamecourse');
$mform->addRule('shortname', get_string('missingshortname'), 'required', null, 'client');
$mform->setType('shortname', PARAM_TEXT);
if (empty($CFG->lockrequestcategory)) {
$displaylist = core_course_category::make_categories_list('moodle/course:request');
$mform->addElement('autocomplete', 'category', get_string('coursecategory'), $displaylist);
$mform->addRule('category', null, 'required', null, 'client');
$mform->setDefault('category', $CFG->defaultrequestcategory);
$mform->addHelpButton('category', 'coursecategory');
}
$mform->addElement('editor', 'summary_editor', get_string('summary'), null, course_request::summary_editor_options());
$mform->addHelpButton('summary_editor', 'coursesummary');
$mform->setType('summary_editor', PARAM_RAW);
$mform->addElement('header','requestreason', get_string('courserequestreason'));
$mform->addElement('textarea', 'reason', get_string('courserequestsupport'), array('rows'=>'15', 'cols'=>'50'));
$mform->addRule('reason', get_string('missingreqreason'), 'required', null, 'client');
$mform->setType('reason', PARAM_TEXT);
$this->add_action_buttons(true, get_string('requestcourse'));
}
function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
$foundcourses = null;
$foundreqcourses = null;
if (!empty($data['shortname'])) {
$foundcourses = $DB->get_records('course', array('shortname'=>$data['shortname']));
$foundreqcourses = $DB->get_records('course_request', array('shortname'=>$data['shortname']));
}
if (!empty($foundreqcourses)) {
if (!empty($foundcourses)) {
$foundcourses = array_merge($foundcourses, $foundreqcourses);
} else {
$foundcourses = $foundreqcourses;
}
}
if (!empty($foundcourses)) {
foreach ($foundcourses as $foundcourse) {
if (!empty($foundcourse->requester)) {
$pending = 1;
$foundcoursenames[] = $foundcourse->fullname.' [*]';
} else {
$foundcoursenames[] = $foundcourse->fullname;
}
}
$foundcoursenamestring = implode(',', $foundcoursenames);
$errors['shortname'] = get_string('shortnametaken', '', $foundcoursenamestring);
if (!empty($pending)) {
$errors['shortname'] .= get_string('starpending');
}
}
return $errors;
}
}
/**
* A form for an administrator to reject a course request.
*/
class reject_request_form extends moodleform {
function definition() {
$mform =& $this->_form;
$mform->addElement('hidden', 'reject', 0);
$mform->setType('reject', PARAM_INT);
$mform->addElement('header','coursedetails', get_string('coursereasonforrejecting'));
$mform->addElement('textarea', 'rejectnotice', get_string('coursereasonforrejectingemail'), array('rows'=>'15', 'cols'=>'50'));
$mform->addRule('rejectnotice', get_string('missingreqreason'), 'required', null, 'client');
$mform->setType('rejectnotice', PARAM_TEXT);
$this->add_action_buttons(true, get_string('reject'));
}
}
@@ -16,23 +16,20 @@
namespace core_course;
use course_request;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/course/lib.php');
use core\context\system as context_system;
use core\context\coursecat as context_coursecat;
use core_course_category;
/**
* Course request related unit tests
* Tests for course_request class.
*
* @package core_course
* @category test
* @copyright 2012 Frédéric Massart
* @copyright Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class courserequest_test extends \advanced_testcase {
#[\PHPUnit\Framework\Attributes\CoversClass(course_request::class)]
final class course_request_test extends \advanced_testcase {
public function test_create_request(): void {
global $DB, $USER;
$this->resetAfterTest(true);
@@ -95,8 +92,12 @@ final class courserequest_test extends \advanced_testcase {
// Create a user and allow course requests for him.
$requester = $this->getDataGenerator()->create_user();
$roleid = create_role('Course requestor role', 'courserequestor', '');
assign_capability('moodle/course:request', CAP_ALLOW, $roleid,
\context_system::instance()->id);
assign_capability(
'moodle/course:request',
CAP_ALLOW,
$roleid,
\context_system::instance()->id
);
role_assign($roleid, $requester->id, \context_system::instance()->id);
accesslib_clear_all_caches_for_unit_testing();
@@ -115,7 +116,7 @@ final class courserequest_test extends \advanced_testcase {
$id = $cr->approve();
$this->assertCount(1, $sink->get_messages_by_component_and_type('core', 'courserequestapproved'));
$sink->close();
$course = $DB->get_record('course', array('id' => $id));
$course = $DB->get_record('course', ['id' => $id]);
$this->assertEquals($data->fullname, $course->fullname);
$this->assertEquals($data->shortname, $course->shortname);
$this->assertEquals($data->summary_editor['text'], $course->summary);
@@ -135,7 +136,7 @@ final class courserequest_test extends \advanced_testcase {
$id = $cr->approve();
$this->assertCount(1, $sink->get_messages_by_component_and_type('core', 'courserequestapproved'));
$sink->close();
$course = $DB->get_record('course', array('id' => $id));
$course = $DB->get_record('course', ['id' => $id]);
$this->assertEquals($data->category, $course->category);
}
@@ -152,8 +153,12 @@ final class courserequest_test extends \advanced_testcase {
// Create a user and allow course requests for him.
$requester = $this->getDataGenerator()->create_user();
$roleid = create_role('Course requestor role', 'courserequestor', '');
assign_capability('moodle/course:request', CAP_ALLOW, $roleid,
\context_system::instance()->id);
assign_capability(
'moodle/course:request',
CAP_ALLOW,
$roleid,
\context_system::instance()->id
);
role_assign($roleid, $requester->id, \context_system::instance()->id);
accesslib_clear_all_caches_for_unit_testing();
@@ -166,13 +171,133 @@ final class courserequest_test extends \advanced_testcase {
$this->setUser($requester);
$cr = course_request::create($data);
$this->assertTrue($DB->record_exists('course_request', array('id' => $cr->id)));
$this->assertTrue($DB->record_exists('course_request', ['id' => $cr->id]));
$this->setAdminUser();
$sink = $this->redirectMessages();
$cr->reject('Sorry!');
$this->assertFalse($DB->record_exists('course_request', array('id' => $cr->id)));
$this->assertFalse($DB->record_exists('course_request', ['id' => $cr->id]));
$this->assertCount(1, $sink->get_messages());
$sink->close();
}
/**
* Tests for the course_request::can_request
*/
public function test_can_request_course(): void {
global $CFG, $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$cat1 = $CFG->defaultrequestcategory;
$cat2 = $this->getDataGenerator()->create_category()->id;
$cat3 = $this->getDataGenerator()->create_category()->id;
$context1 = context_coursecat::instance($cat1);
$context2 = context_coursecat::instance($cat2);
$context3 = context_coursecat::instance($cat3);
$this->setUser($user);
// By default users don't have capability to request courses.
$this->assertFalse(course_request::can_request(context_system::instance()));
$this->assertFalse(course_request::can_request($context1));
$this->assertFalse(course_request::can_request($context2));
$this->assertFalse(course_request::can_request($context3));
// Allow for the 'user' role the capability to request courses.
$userroleid = $DB->get_field('role', 'id', ['shortname' => 'user']);
assign_capability(
'moodle/course:request',
CAP_ALLOW,
$userroleid,
context_system::instance()->id
);
accesslib_clear_all_caches_for_unit_testing();
// Lock category selection.
$CFG->lockrequestcategory = 1;
// Now user can only request course in the default category or in system context.
$this->assertTrue(course_request::can_request(context_system::instance()));
$this->assertTrue(course_request::can_request($context1));
$this->assertFalse(course_request::can_request($context2));
$this->assertFalse(course_request::can_request($context3));
// Enable category selection. User can request course anywhere.
$CFG->lockrequestcategory = 0;
$this->assertTrue(course_request::can_request(context_system::instance()));
$this->assertTrue(course_request::can_request($context1));
$this->assertTrue(course_request::can_request($context2));
$this->assertTrue(course_request::can_request($context3));
// Remove cap from cat2.
$roleid = create_role('Test role', 'testrole', 'Test role description');
assign_capability(
'moodle/course:request',
CAP_PROHIBIT,
$roleid,
$context2->id,
true
);
role_assign($roleid, $user->id, $context2->id);
accesslib_clear_all_caches_for_unit_testing();
$this->assertTrue(course_request::can_request(context_system::instance()));
$this->assertTrue(course_request::can_request($context1));
$this->assertFalse(course_request::can_request($context2));
$this->assertTrue(course_request::can_request($context3));
// Disable course request functionality.
$CFG->enablecourserequests = false;
$this->assertFalse(course_request::can_request(context_system::instance()));
$this->assertFalse(course_request::can_request($context1));
$this->assertFalse(course_request::can_request($context2));
$this->assertFalse(course_request::can_request($context3));
}
/**
* Tests for the course_request::can_approve
*/
public function test_can_approve_course_request(): void {
global $CFG;
$this->resetAfterTest();
$requestor = $this->getDataGenerator()->create_user();
$user = $this->getDataGenerator()->create_user();
$cat1 = $CFG->defaultrequestcategory;
$cat2 = $this->getDataGenerator()->create_category()->id;
$cat3 = $this->getDataGenerator()->create_category()->id;
// Enable course requests. Default 'user' role has capability to request courses.
$CFG->enablecourserequests = true;
$CFG->lockrequestcategory = 0;
$this->setUser($requestor);
$requestdata = ['summary_editor' => ['text' => '', 'format' => 0], 'name' => 'Req', 'reason' => 'test'];
$request1 = course_request::create((object)($requestdata));
$request2 = course_request::create((object)($requestdata + ['category' => $cat2]));
$request3 = course_request::create((object)($requestdata + ['category' => $cat3]));
$this->setUser($user);
// Add capability to approve courses.
$roleid = create_role('Test role', 'testrole', 'Test role description');
assign_capability(
'moodle/site:approvecourse',
CAP_ALLOW,
$roleid,
context_system::instance()->id,
true
);
role_assign($roleid, $user->id, context_coursecat::instance($cat2)->id);
accesslib_clear_all_caches_for_unit_testing();
$this->assertFalse($request1->can_approve());
$this->assertTrue($request2->can_approve());
$this->assertFalse($request3->can_approve());
// Delete category where course was requested. Now only site-wide manager can approve it.
core_course_category::get($cat2, MUST_EXIST, true)->delete_full(false);
$this->assertFalse($request2->can_approve());
$this->setAdminUser();
$this->assertTrue($request2->can_approve());
}
}
-107
View File
@@ -35,7 +35,6 @@ use core_external;
use core_tag_index_builder;
use core_tag_tag;
use course_capability_assignment;
use course_request;
use core_course_category;
use enrol_imsenterprise\imsenterprise_test;
use core_external\external_api;
@@ -7275,112 +7274,6 @@ final class courselib_test extends advanced_testcase {
$this->assertEquals($expected, course_modules_pending_deletion($course->id, $gradable));
}
/**
* Tests for the course_request::can_request
*/
public function test_can_request_course(): void {
global $CFG, $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$cat1 = $CFG->defaultrequestcategory;
$cat2 = $this->getDataGenerator()->create_category()->id;
$cat3 = $this->getDataGenerator()->create_category()->id;
$context1 = context_coursecat::instance($cat1);
$context2 = context_coursecat::instance($cat2);
$context3 = context_coursecat::instance($cat3);
$this->setUser($user);
// By default users don't have capability to request courses.
$this->assertFalse(course_request::can_request(context_system::instance()));
$this->assertFalse(course_request::can_request($context1));
$this->assertFalse(course_request::can_request($context2));
$this->assertFalse(course_request::can_request($context3));
// Allow for the 'user' role the capability to request courses.
$userroleid = $DB->get_field('role', 'id', ['shortname' => 'user']);
assign_capability('moodle/course:request', CAP_ALLOW, $userroleid,
context_system::instance()->id);
accesslib_clear_all_caches_for_unit_testing();
// Lock category selection.
$CFG->lockrequestcategory = 1;
// Now user can only request course in the default category or in system context.
$this->assertTrue(course_request::can_request(context_system::instance()));
$this->assertTrue(course_request::can_request($context1));
$this->assertFalse(course_request::can_request($context2));
$this->assertFalse(course_request::can_request($context3));
// Enable category selection. User can request course anywhere.
$CFG->lockrequestcategory = 0;
$this->assertTrue(course_request::can_request(context_system::instance()));
$this->assertTrue(course_request::can_request($context1));
$this->assertTrue(course_request::can_request($context2));
$this->assertTrue(course_request::can_request($context3));
// Remove cap from cat2.
$roleid = create_role('Test role', 'testrole', 'Test role description');
assign_capability('moodle/course:request', CAP_PROHIBIT, $roleid,
$context2->id, true);
role_assign($roleid, $user->id, $context2->id);
accesslib_clear_all_caches_for_unit_testing();
$this->assertTrue(course_request::can_request(context_system::instance()));
$this->assertTrue(course_request::can_request($context1));
$this->assertFalse(course_request::can_request($context2));
$this->assertTrue(course_request::can_request($context3));
// Disable course request functionality.
$CFG->enablecourserequests = false;
$this->assertFalse(course_request::can_request(context_system::instance()));
$this->assertFalse(course_request::can_request($context1));
$this->assertFalse(course_request::can_request($context2));
$this->assertFalse(course_request::can_request($context3));
}
/**
* Tests for the course_request::can_approve
*/
public function test_can_approve_course_request(): void {
global $CFG;
$this->resetAfterTest();
$requestor = $this->getDataGenerator()->create_user();
$user = $this->getDataGenerator()->create_user();
$cat1 = $CFG->defaultrequestcategory;
$cat2 = $this->getDataGenerator()->create_category()->id;
$cat3 = $this->getDataGenerator()->create_category()->id;
// Enable course requests. Default 'user' role has capability to request courses.
$CFG->enablecourserequests = true;
$CFG->lockrequestcategory = 0;
$this->setUser($requestor);
$requestdata = ['summary_editor' => ['text' => '', 'format' => 0], 'name' => 'Req', 'reason' => 'test'];
$request1 = course_request::create((object)($requestdata));
$request2 = course_request::create((object)($requestdata + ['category' => $cat2]));
$request3 = course_request::create((object)($requestdata + ['category' => $cat3]));
$this->setUser($user);
// Add capability to approve courses.
$roleid = create_role('Test role', 'testrole', 'Test role description');
assign_capability('moodle/site:approvecourse', CAP_ALLOW, $roleid,
context_system::instance()->id, true);
role_assign($roleid, $user->id, context_coursecat::instance($cat2)->id);
accesslib_clear_all_caches_for_unit_testing();
$this->assertFalse($request1->can_approve());
$this->assertTrue($request2->can_approve());
$this->assertFalse($request3->can_approve());
// Delete category where course was requested. Now only site-wide manager can approve it.
core_course_category::get($cat2, MUST_EXIST, true)->delete_full(false);
$this->assertFalse($request2->can_approve());
$this->setAdminUser();
$this->assertTrue($request2->can_approve());
}
/**
* Test the course allowed module method.
*/
+14
View File
@@ -287,6 +287,16 @@ $legacyclasses = [
'dataformat_export_format.php',
],
// Course drag-and-drop upload system.
\dndupload_handler::class => [
'core_course',
'dndupload_handler.php',
],
\dndupload_ajax_processor::class => [
'core_course',
'dndupload_ajax_processor.php',
],
// The progress_trace classes.
\combined_progress_trace::class => 'output/progress_trace/combined_progress_trace.php',
\error_log_progress_trace::class => 'output/progress_trace/error_log_progress_trace.php',
@@ -346,4 +356,8 @@ $legacyclasses = [
'core_comment',
'comment_exception.php',
],
\course_request::class => [
'core_course',
'course_request.php',
],
];