Files
moodle/public/course/classes/course_request.php
T
Paul Holden 156c6f69ac MDL-87120 course: observe activity dates display in course requests.
Upgrade step to ensure the default value for this field in the schema
matches the default value used in the interface itself.
2026-01-07 16:43:16 +00:00

472 lines
17 KiB
PHP

<?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\{clock, di};
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')) {
$category = \core_course_category::get($data->category);
$a = new stdClass();
$a->link = "$CFG->wwwroot/course/pending.php";
$a->user = fullname($USER);
$a->shortname = format_string($data->shortname, true, ['context' => $category->get_context()]);
$a->fullname = format_string($data->fullname, true, ['context' => $category->get_context()]);
$a->category = $category->get_formatted_name();
$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->showactivitydates = $courseconfig->showactivitydates;
$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(di::get(clock::class)->time());
if ($courseconfig->courseenddateenabled) {
$data->enddate = $data->startdate + $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);