Commit | Line | Data |
---|---|---|
df997f84 | 1 | <?php |
df997f84 PS |
2 | // This file is part of Moodle - http://moodle.org/ |
3 | // | |
4 | // Moodle is free software: you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License as published by | |
6 | // the Free Software Foundation, either version 3 of the License, or | |
7 | // (at your option) any later version. | |
8 | // | |
9 | // Moodle is distributed in the hope that it will be useful, | |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | // GNU General Public License for more details. | |
13 | // | |
14 | // You should have received a copy of the GNU General Public License | |
15 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
17 | /** | |
18 | * Meta course enrolment plugin. | |
19 | * | |
31ac2aef | 20 | * @package enrol_meta |
e2382027 PS |
21 | * @copyright 2010 Petr Skoda {@link http://skodak.org} |
22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
df997f84 PS |
23 | */ |
24 | ||
e2382027 | 25 | defined('MOODLE_INTERNAL') || die(); |
df997f84 | 26 | |
e284e179 MG |
27 | /** |
28 | * ENROL_META_CREATE_GROUP constant for automatically creating a group for a meta course. | |
29 | */ | |
30 | define('ENROL_META_CREATE_GROUP', -1); | |
31 | ||
df997f84 PS |
32 | /** |
33 | * Meta course enrolment plugin. | |
34 | * @author Petr Skoda | |
35 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
36 | */ | |
37 | class enrol_meta_plugin extends enrol_plugin { | |
38 | ||
39 | /** | |
40 | * Returns localised name of enrol instance | |
41 | * | |
ad68ed74 | 42 | * @param stdClass $instance (null is accepted too) |
df997f84 PS |
43 | * @return string |
44 | */ | |
45 | public function get_instance_name($instance) { | |
46 | global $DB; | |
47 | ||
48 | if (empty($instance)) { | |
49 | $enrol = $this->get_name(); | |
50 | return get_string('pluginname', 'enrol_'.$enrol); | |
51 | } else if (empty($instance->name)) { | |
52 | $enrol = $this->get_name(); | |
70f4ac5a | 53 | $course = $DB->get_record('course', array('id'=>$instance->customint1)); |
b7a5eb1e RT |
54 | if ($course) { |
55 | $coursename = format_string(get_course_display_name_for_list($course)); | |
56 | } else { | |
57 | // Use course id, if course is deleted. | |
58 | $coursename = $instance->customint1; | |
59 | } | |
70f4ac5a | 60 | return get_string('pluginname', 'enrol_' . $enrol) . ' (' . $coursename . ')'; |
df997f84 PS |
61 | } else { |
62 | return format_string($instance->name); | |
63 | } | |
64 | } | |
65 | ||
66 | /** | |
60010fd6 DW |
67 | * Returns true if we can add a new instance to this course. |
68 | * | |
df997f84 | 69 | * @param int $courseid |
60010fd6 | 70 | * @return boolean |
df997f84 | 71 | */ |
60010fd6 | 72 | public function can_add_instance($courseid) { |
55bcef29 | 73 | $context = context_course::instance($courseid, MUST_EXIST); |
e2382027 | 74 | if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/meta:config', $context)) { |
60010fd6 | 75 | return false; |
df997f84 | 76 | } |
60010fd6 DW |
77 | // Multiple instances supported - multiple parent courses linked. |
78 | return true; | |
df997f84 PS |
79 | } |
80 | ||
ad68ed74 PS |
81 | /** |
82 | * Does this plugin allow manual unenrolment of a specific user? | |
83 | * Yes, but only if user suspended... | |
84 | * | |
85 | * @param stdClass $instance course enrol instance | |
86 | * @param stdClass $ue record from user_enrolments table | |
87 | * | |
88 | * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol this user, false means nobody may touch this user enrolment | |
89 | */ | |
90 | public function allow_unenrol_user(stdClass $instance, stdClass $ue) { | |
91 | if ($ue->status == ENROL_USER_SUSPENDED) { | |
92 | return true; | |
93 | } | |
94 | ||
95 | return false; | |
96 | } | |
97 | ||
98 | /** | |
99 | * Gets an array of the user enrolment actions | |
100 | * | |
101 | * @param course_enrolment_manager $manager | |
102 | * @param stdClass $ue A user enrolment object | |
103 | * @return array An array of user_enrolment_actions | |
104 | */ | |
105 | public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) { | |
106 | $actions = array(); | |
107 | $context = $manager->get_context(); | |
108 | $instance = $ue->enrolmentinstance; | |
109 | $params = $manager->get_moodlepage()->url->params(); | |
110 | $params['ue'] = $ue->id; | |
111 | if ($this->allow_unenrol_user($instance, $ue) && has_capability('enrol/meta:unenrol', $context)) { | |
112 | $url = new moodle_url('/enrol/unenroluser.php', $params); | |
fd0a43be JP |
113 | $actionparams = array('class' => 'unenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_UNENROL); |
114 | $actions[] = new user_enrolment_action(new pix_icon('t/delete', ''), get_string('unenrol', 'enrol'), $url, | |
115 | $actionparams); | |
ad68ed74 PS |
116 | } |
117 | return $actions; | |
118 | } | |
df997f84 PS |
119 | |
120 | /** | |
121 | * Called after updating/inserting course. | |
122 | * | |
123 | * @param bool $inserted true if course just inserted | |
ad68ed74 PS |
124 | * @param stdClass $course |
125 | * @param stdClass $data form data | |
df997f84 PS |
126 | * @return void |
127 | */ | |
128 | public function course_updated($inserted, $course, $data) { | |
fa151f7a PS |
129 | // Meta sync updates are slow, if enrolments get out of sync teacher will have to wait till next cron. |
130 | // We should probably add some sync button to the course enrol methods overview page. | |
ad68ed74 PS |
131 | } |
132 | ||
60010fd6 DW |
133 | /** |
134 | * Add new instance of enrol plugin. | |
135 | * @param object $course | |
136 | * @param array $fields instance fields | |
6f326bd2 | 137 | * @return int id of last instance, null if can not be created |
60010fd6 DW |
138 | */ |
139 | public function add_instance($course, array $fields = null) { | |
140 | global $CFG; | |
141 | ||
142 | require_once("$CFG->dirroot/enrol/meta/locallib.php"); | |
143 | ||
6f326bd2 DW |
144 | // Support creating multiple at once. |
145 | if (is_array($fields['customint1'])) { | |
146 | $courses = array_unique($fields['customint1']); | |
147 | } else { | |
148 | $courses = array($fields['customint1']); | |
60010fd6 | 149 | } |
6f326bd2 DW |
150 | foreach ($courses as $courseid) { |
151 | if (!empty($fields['customint2']) && $fields['customint2'] == ENROL_META_CREATE_GROUP) { | |
152 | $context = context_course::instance($course->id); | |
153 | require_capability('moodle/course:managegroups', $context); | |
154 | $groupid = enrol_meta_create_new_group($course->id, $courseid); | |
155 | $fields['customint2'] = $groupid; | |
156 | } | |
60010fd6 | 157 | |
6f326bd2 DW |
158 | $fields['customint1'] = $courseid; |
159 | $result = parent::add_instance($course, $fields); | |
160 | } | |
60010fd6 DW |
161 | |
162 | enrol_meta_sync($course->id); | |
163 | ||
164 | return $result; | |
165 | } | |
166 | ||
167 | /** | |
168 | * Update instance of enrol plugin. | |
169 | * @param stdClass $instance | |
170 | * @param stdClass $data modified instance fields | |
171 | * @return boolean | |
172 | */ | |
173 | public function update_instance($instance, $data) { | |
174 | global $CFG; | |
175 | ||
176 | require_once("$CFG->dirroot/enrol/meta/locallib.php"); | |
177 | ||
178 | if (!empty($data->customint2) && $data->customint2 == ENROL_META_CREATE_GROUP) { | |
179 | $context = context_course::instance($instance->courseid); | |
180 | require_capability('moodle/course:managegroups', $context); | |
181 | $groupid = enrol_meta_create_new_group($instance->courseid, $data->customint1); | |
182 | $data->customint2 = $groupid; | |
183 | } | |
184 | ||
185 | $result = parent::update_instance($instance, $data); | |
186 | ||
187 | enrol_meta_sync($instance->courseid); | |
188 | ||
189 | return $result; | |
190 | } | |
191 | ||
ad68ed74 PS |
192 | /** |
193 | * Update instance status | |
194 | * | |
195 | * @param stdClass $instance | |
196 | * @param int $newstatus ENROL_INSTANCE_ENABLED, ENROL_INSTANCE_DISABLED | |
197 | * @return void | |
198 | */ | |
199 | public function update_status($instance, $newstatus) { | |
200 | global $CFG; | |
201 | ||
202 | parent::update_status($instance, $newstatus); | |
df997f84 | 203 | |
ad68ed74 PS |
204 | require_once("$CFG->dirroot/enrol/meta/locallib.php"); |
205 | enrol_meta_sync($instance->courseid); | |
df997f84 PS |
206 | } |
207 | ||
208 | /** | |
209 | * Called for all enabled enrol plugins that returned true from is_cron_required(). | |
210 | * @return void | |
211 | */ | |
212 | public function cron() { | |
213 | global $CFG; | |
214 | ||
df997f84 PS |
215 | require_once("$CFG->dirroot/enrol/meta/locallib.php"); |
216 | enrol_meta_sync(); | |
217 | } | |
ee9e079d DN |
218 | |
219 | /** | |
220 | * Is it possible to delete enrol instance via standard UI? | |
221 | * | |
b5a289c4 | 222 | * @param stdClass $instance |
ee9e079d DN |
223 | * @return bool |
224 | */ | |
225 | public function can_delete_instance($instance) { | |
226 | $context = context_course::instance($instance->courseid); | |
227 | return has_capability('enrol/meta:config', $context); | |
228 | } | |
df997f84 | 229 | |
b5a289c4 DNA |
230 | /** |
231 | * Is it possible to hide/show enrol instance via standard UI? | |
232 | * | |
233 | * @param stdClass $instance | |
234 | * @return bool | |
235 | */ | |
236 | public function can_hide_show_instance($instance) { | |
237 | $context = context_course::instance($instance->courseid); | |
238 | return has_capability('enrol/meta:config', $context); | |
239 | } | |
9e37d365 | 240 | |
60010fd6 DW |
241 | /** |
242 | * We are a good plugin and don't invent our own UI/validation code path. | |
243 | * | |
244 | * @return boolean | |
245 | */ | |
246 | public function use_standard_editing_ui() { | |
247 | return true; | |
248 | } | |
249 | ||
250 | /** | |
251 | * Return an array of valid options for the courses. | |
252 | * | |
253 | * @param stdClass $instance | |
254 | * @param context $coursecontext | |
255 | * @return array | |
256 | */ | |
257 | protected function get_course_options($instance, $coursecontext) { | |
258 | global $DB; | |
259 | ||
260 | if ($instance->id) { | |
261 | $where = 'WHERE c.id = :courseid'; | |
262 | $params = array('courseid' => $instance->customint1); | |
263 | $existing = array(); | |
264 | } else { | |
265 | $where = ''; | |
266 | $params = array(); | |
267 | $instanceparams = array('enrol' => 'meta', 'courseid' => $instance->courseid); | |
268 | $existing = $DB->get_records('enrol', $instanceparams, '', 'customint1, id'); | |
269 | } | |
270 | ||
271 | // TODO: this has to be done via ajax or else it will fail very badly on large sites! | |
6f326bd2 | 272 | $courses = array(); |
60010fd6 DW |
273 | $select = ', ' . context_helper::get_preload_record_columns_sql('ctx'); |
274 | $join = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)"; | |
275 | ||
276 | $sortorder = 'c.' . $this->get_config('coursesort', 'sortorder') . ' ASC'; | |
277 | ||
278 | $sql = "SELECT c.id, c.fullname, c.shortname, c.visible $select FROM {course} c $join $where ORDER BY $sortorder"; | |
279 | $rs = $DB->get_recordset_sql($sql, array('contextlevel' => CONTEXT_COURSE) + $params); | |
280 | foreach ($rs as $c) { | |
281 | if ($c->id == SITEID or $c->id == $instance->courseid or isset($existing[$c->id])) { | |
282 | continue; | |
283 | } | |
284 | context_helper::preload_from_record($c); | |
285 | $coursecontext = context_course::instance($c->id); | |
286 | if (!$c->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) { | |
287 | continue; | |
288 | } | |
289 | if (!has_capability('enrol/meta:selectaslinked', $coursecontext)) { | |
290 | continue; | |
291 | } | |
292 | $courses[$c->id] = $coursecontext->get_context_name(false); | |
293 | } | |
294 | $rs->close(); | |
295 | return $courses; | |
296 | } | |
297 | ||
298 | /** | |
299 | * Return an array of valid options for the groups. | |
300 | * | |
301 | * @param context $coursecontext | |
302 | * @return array | |
303 | */ | |
304 | protected function get_group_options($coursecontext) { | |
305 | $groups = array(0 => get_string('none')); | |
306 | $courseid = $coursecontext->instanceid; | |
307 | if (has_capability('moodle/course:managegroups', $coursecontext)) { | |
308 | $groups[ENROL_META_CREATE_GROUP] = get_string('creategroup', 'enrol_meta'); | |
309 | } | |
310 | foreach (groups_get_all_groups($courseid) as $group) { | |
311 | $groups[$group->id] = format_string($group->name, true, array('context' => $coursecontext)); | |
312 | } | |
313 | return $groups; | |
314 | } | |
315 | ||
316 | /** | |
317 | * Add elements to the edit instance form. | |
318 | * | |
319 | * @param stdClass $instance | |
320 | * @param MoodleQuickForm $mform | |
321 | * @param context $coursecontext | |
322 | * @return bool | |
323 | */ | |
324 | public function edit_instance_form($instance, MoodleQuickForm $mform, $coursecontext) { | |
325 | global $DB; | |
326 | ||
60010fd6 | 327 | $groups = $this->get_group_options($coursecontext); |
6b046555 DW |
328 | $existing = $DB->get_records('enrol', array('enrol' => 'meta', 'courseid' => $coursecontext->instanceid), '', 'customint1, id'); |
329 | ||
330 | $excludelist = array($coursecontext->instanceid); | |
331 | foreach ($existing as $existinginstance) { | |
332 | $excludelist[] = $existinginstance->customint1; | |
333 | } | |
60010fd6 | 334 | |
6f326bd2 DW |
335 | $options = array( |
336 | 'requiredcapabilities' => array('enrol/meta:selectaslinked'), | |
77bec234 | 337 | 'multiple' => empty($instance->id), // We only accept multiple values on creation. |
6b046555 | 338 | 'exclude' => $excludelist |
6f326bd2 DW |
339 | ); |
340 | $mform->addElement('course', 'customint1', get_string('linkedcourse', 'enrol_meta'), $options); | |
60010fd6 DW |
341 | $mform->addRule('customint1', get_string('required'), 'required', null, 'client'); |
342 | if (!empty($instance->id)) { | |
343 | $mform->freeze('customint1'); | |
344 | } | |
345 | ||
346 | $mform->addElement('select', 'customint2', get_string('addgroup', 'enrol_meta'), $groups); | |
347 | } | |
348 | ||
349 | /** | |
350 | * Perform custom validation of the data used to edit the instance. | |
351 | * | |
352 | * @param array $data array of ("fieldname"=>value) of submitted data | |
353 | * @param array $files array of uploaded files "element_name"=>tmp_file_path | |
354 | * @param object $instance The instance loaded from the DB | |
355 | * @param context $context The context of the instance we are editing | |
356 | * @return array of "element_name"=>"error_description" if there are errors, | |
357 | * or an empty array if everything is OK. | |
358 | * @return void | |
359 | */ | |
360 | public function edit_instance_validation($data, $files, $instance, $context) { | |
361 | global $DB; | |
362 | $errors = array(); | |
363 | $thiscourseid = $context->instanceid; | |
364 | $c = false; | |
365 | ||
366 | if (!empty($data['customint1'])) { | |
77bec234 FM |
367 | $courses = is_array($data['customint1']) ? $data['customint1'] : [$data['customint1']]; |
368 | foreach ($courses as $courseid) { | |
6f326bd2 DW |
369 | $c = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); |
370 | $coursecontext = context_course::instance($c->id); | |
77bec234 FM |
371 | |
372 | $sqlexists = 'enrol = :meta AND courseid = :currentcourseid AND customint1 = :courseid AND id != :id'; | |
373 | $existing = $DB->record_exists_select('enrol', $sqlexists, [ | |
374 | 'meta' => 'meta', | |
375 | 'currentcourseid' => $thiscourseid, | |
376 | 'courseid' => $c->id, | |
377 | 'id' => $instance->id | |
378 | ]); | |
379 | ||
6f326bd2 DW |
380 | if (!$c->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) { |
381 | $errors['customint1'] = get_string('error'); | |
382 | } else if (!has_capability('enrol/meta:selectaslinked', $coursecontext)) { | |
383 | $errors['customint1'] = get_string('error'); | |
77bec234 | 384 | } else if ($c->id == SITEID or $c->id == $thiscourseid or $existing) { |
6f326bd2 DW |
385 | $errors['customint1'] = get_string('error'); |
386 | } | |
60010fd6 | 387 | } |
6f326bd2 DW |
388 | } else { |
389 | $errors['customint1'] = get_string('required'); | |
60010fd6 DW |
390 | } |
391 | ||
60010fd6 DW |
392 | $validgroups = array_keys($this->get_group_options($context)); |
393 | ||
394 | $tovalidate = array( | |
60010fd6 DW |
395 | 'customint2' => $validgroups |
396 | ); | |
397 | $typeerrors = $this->validate_param_types($data, $tovalidate); | |
398 | $errors = array_merge($errors, $typeerrors); | |
399 | ||
400 | return $errors; | |
401 | } | |
402 | ||
403 | ||
9e37d365 MG |
404 | /** |
405 | * Restore instance and map settings. | |
406 | * | |
407 | * @param restore_enrolments_structure_step $step | |
408 | * @param stdClass $data | |
409 | * @param stdClass $course | |
410 | * @param int $oldid | |
411 | */ | |
412 | public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) { | |
413 | global $DB, $CFG; | |
414 | ||
415 | if (!$step->get_task()->is_samesite()) { | |
416 | // No meta restore from other sites. | |
417 | $step->set_mapping('enrol', $oldid, 0); | |
418 | return; | |
419 | } | |
420 | ||
421 | if (!empty($data->customint2)) { | |
422 | $data->customint2 = $step->get_mappingid('group', $data->customint2); | |
423 | } | |
424 | ||
425 | if ($DB->record_exists('course', array('id' => $data->customint1))) { | |
426 | $instance = $DB->get_record('enrol', array('roleid' => $data->roleid, 'customint1' => $data->customint1, | |
427 | 'courseid' => $course->id, 'enrol' => $this->get_name())); | |
428 | if ($instance) { | |
429 | $instanceid = $instance->id; | |
430 | } else { | |
431 | $instanceid = $this->add_instance($course, (array)$data); | |
432 | } | |
433 | $step->set_mapping('enrol', $oldid, $instanceid); | |
434 | ||
435 | require_once("$CFG->dirroot/enrol/meta/locallib.php"); | |
436 | enrol_meta_sync($data->customint1); | |
437 | ||
438 | } else { | |
439 | $step->set_mapping('enrol', $oldid, 0); | |
440 | } | |
441 | } | |
442 | ||
443 | /** | |
444 | * Restore user enrolment. | |
445 | * | |
446 | * @param restore_enrolments_structure_step $step | |
447 | * @param stdClass $data | |
448 | * @param stdClass $instance | |
449 | * @param int $userid | |
450 | * @param int $oldinstancestatus | |
451 | */ | |
452 | public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) { | |
453 | global $DB; | |
454 | ||
455 | if ($this->get_config('unenrolaction') != ENROL_EXT_REMOVED_SUSPENDNOROLES) { | |
456 | // Enrolments were already synchronised in restore_instance(), we do not want any suspended leftovers. | |
457 | return; | |
458 | } | |
459 | ||
460 | // ENROL_EXT_REMOVED_SUSPENDNOROLES means all previous enrolments are restored | |
461 | // but without roles and suspended. | |
462 | ||
463 | if (!$DB->record_exists('user_enrolments', array('enrolid' => $instance->id, 'userid' => $userid))) { | |
464 | $this->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, ENROL_USER_SUSPENDED); | |
465 | if ($instance->customint2) { | |
466 | groups_add_member($instance->customint2, $userid, 'enrol_meta', $instance->id); | |
467 | } | |
468 | } | |
469 | } | |
470 | ||
471 | /** | |
472 | * Restore user group membership. | |
473 | * @param stdClass $instance | |
474 | * @param int $groupid | |
475 | * @param int $userid | |
476 | */ | |
477 | public function restore_group_member($instance, $groupid, $userid) { | |
478 | // Nothing to do here, the group members are added in $this->restore_group_restored(). | |
479 | return; | |
480 | } | |
bacb7b1f | 481 | |
b5a289c4 | 482 | } |