Merge branch 'MDL-82322-main' of https://github.com/andrewnicols/moodle
This commit is contained in:
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
|
||||
+143
-18
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user