Commit | Line | Data |
---|---|---|
a580b337 SR |
1 | <?php |
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 | * Fee enrolment plugin. | |
19 | * | |
20 | * This plugin allows you to set up paid courses. | |
21 | * | |
22 | * @package enrol_fee | |
23 | * @copyright 2019 Shamim Rezaie <shamim@moodle.com> | |
24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
25 | */ | |
26 | ||
27 | /** | |
28 | * Fee enrolment plugin implementation. | |
29 | * | |
30 | * @copyright 2019 Shamim Rezaie <shamim@moodle.com> | |
31 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
32 | */ | |
33 | class enrol_fee_plugin extends enrol_plugin { | |
34 | ||
35 | /** | |
36 | * Returns the list of currencies that the payment subsystem supports and therefore we can work with. | |
37 | * | |
38 | * @return array[currencycode => currencyname] | |
39 | */ | |
40 | public function get_possible_currencies(): array { | |
41 | $codes = \core_payment\helper::get_supported_currencies(); | |
42 | ||
43 | $currencies = []; | |
44 | foreach ($codes as $c) { | |
45 | $currencies[$c] = new lang_string($c, 'core_currencies'); | |
46 | } | |
47 | ||
48 | return $currencies; | |
49 | } | |
50 | ||
51 | /** | |
52 | * Returns optional enrolment information icons. | |
53 | * | |
54 | * This is used in course list for quick overview of enrolment options. | |
55 | * | |
56 | * We are not using single instance parameter because sometimes | |
57 | * we might want to prevent icon repetition when multiple instances | |
58 | * of one type exist. One instance may also produce several icons. | |
59 | * | |
60 | * @param array $instances all enrol instances of this type in one course | |
61 | * @return array of pix_icon | |
62 | */ | |
63 | public function get_info_icons(array $instances) { | |
64 | $found = false; | |
65 | foreach ($instances as $instance) { | |
66 | if ($instance->enrolstartdate != 0 && $instance->enrolstartdate > time()) { | |
67 | continue; | |
68 | } | |
69 | if ($instance->enrolenddate != 0 && $instance->enrolenddate < time()) { | |
70 | continue; | |
71 | } | |
72 | $found = true; | |
73 | break; | |
74 | } | |
75 | if ($found) { | |
76 | return array(new pix_icon('icon', get_string('pluginname', 'enrol_fee'), 'enrol_fee')); | |
77 | } | |
78 | return array(); | |
79 | } | |
80 | ||
81 | public function roles_protected() { | |
82 | // Users with role assign cap may tweak the roles later. | |
83 | return false; | |
84 | } | |
85 | ||
86 | public function allow_unenrol(stdClass $instance) { | |
87 | // Users with unenrol cap may unenrol other users manually - requires enrol/fee:unenrol. | |
88 | return true; | |
89 | } | |
90 | ||
91 | public function allow_manage(stdClass $instance) { | |
92 | // Users with manage cap may tweak period and status - requires enrol/fee:manage. | |
93 | return true; | |
94 | } | |
95 | ||
96 | public function show_enrolme_link(stdClass $instance) { | |
97 | return ($instance->status == ENROL_INSTANCE_ENABLED); | |
98 | } | |
99 | ||
100 | /** | |
101 | * Returns true if the user can add a new instance in this course. | |
102 | * @param int $courseid | |
103 | * @return boolean | |
104 | */ | |
105 | public function can_add_instance($courseid) { | |
106 | $context = context_course::instance($courseid, MUST_EXIST); | |
107 | ||
108 | if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/fee:config', $context)) { | |
109 | return false; | |
110 | } | |
111 | ||
112 | // Multiple instances supported - different cost for different roles. | |
113 | return true; | |
114 | } | |
115 | ||
116 | /** | |
117 | * We are a good plugin and don't invent our own UI/validation code path. | |
118 | * | |
119 | * @return boolean | |
120 | */ | |
121 | public function use_standard_editing_ui() { | |
122 | return true; | |
123 | } | |
124 | ||
125 | /** | |
126 | * Add new instance of enrol plugin. | |
127 | * @param object $course | |
128 | * @param array $fields instance fields | |
129 | * @return int id of new instance, null if can not be created | |
130 | */ | |
131 | public function add_instance($course, array $fields = null) { | |
132 | if ($fields && !empty($fields['cost'])) { | |
133 | $fields['cost'] = unformat_float($fields['cost']); | |
134 | } | |
135 | return parent::add_instance($course, $fields); | |
136 | } | |
137 | ||
138 | /** | |
139 | * Update instance of enrol plugin. | |
140 | * @param stdClass $instance | |
141 | * @param stdClass $data modified instance fields | |
142 | * @return boolean | |
143 | */ | |
144 | public function update_instance($instance, $data) { | |
145 | if ($data) { | |
146 | $data->cost = unformat_float($data->cost); | |
147 | } | |
148 | return parent::update_instance($instance, $data); | |
149 | } | |
150 | ||
151 | /** | |
152 | * Creates course enrol form, checks if form submitted | |
153 | * and enrols user if necessary. It can also redirect. | |
154 | * | |
155 | * @param stdClass $instance | |
156 | * @return string html text, usually a form in a text box | |
157 | */ | |
158 | public function enrol_page_hook(stdClass $instance) { | |
159 | global $CFG, $USER, $OUTPUT, $PAGE, $DB; | |
160 | ||
161 | ob_start(); | |
162 | ||
163 | if ($DB->record_exists('user_enrolments', array('userid' => $USER->id, 'enrolid' => $instance->id))) { | |
164 | return ob_get_clean(); | |
165 | } | |
166 | ||
167 | if ($instance->enrolstartdate != 0 && $instance->enrolstartdate > time()) { | |
168 | return ob_get_clean(); | |
169 | } | |
170 | ||
171 | if ($instance->enrolenddate != 0 && $instance->enrolenddate < time()) { | |
172 | return ob_get_clean(); | |
173 | } | |
174 | ||
175 | $course = $DB->get_record('course', array('id' => $instance->courseid)); | |
176 | $context = context_course::instance($course->id); | |
177 | ||
178 | $shortname = format_string($course->shortname, true, array('context' => $context)); | |
179 | $strloginto = get_string("loginto", "", $shortname); | |
180 | $strcourses = get_string("courses"); | |
181 | ||
182 | // Pass $view=true to filter hidden caps if the user cannot see them. | |
183 | if ($users = get_users_by_capability($context, 'moodle/course:update', 'u.*', 'u.id ASC', | |
184 | '', '', '', '', false, true)) { | |
185 | $users = sort_by_roleassignment_authority($users, $context); | |
186 | $teacher = array_shift($users); | |
187 | } else { | |
188 | $teacher = false; | |
189 | } | |
190 | ||
191 | if ( (float) $instance->cost <= 0 ) { | |
192 | $cost = (float) $this->get_config('cost'); | |
193 | } else { | |
194 | $cost = (float) $instance->cost; | |
195 | } | |
196 | ||
197 | if (abs($cost) < 0.01) { // No cost, other enrolment methods (instances) should be used. | |
198 | echo '<p>'.get_string('nocost', 'enrol_fee').'</p>'; | |
199 | } else { | |
200 | $localisedcost = format_float($cost, -1, true); | |
201 | ||
202 | if (isguestuser()) { // Force login only for guest user, not real users with guest role. | |
203 | $wwwroot = $CFG->wwwroot; | |
204 | echo '<div class="mdl-align"><p>'.get_string('paymentrequired').'</p>'; | |
205 | echo '<p><b>'.get_string('cost').": $instance->currency $localisedcost".'</b></p>'; | |
206 | echo '<p><a href="'.$wwwroot.'/login/">'.get_string('loginsite').'</a></p>'; | |
207 | echo '</div>'; | |
208 | } else { | |
e88e3b87 | 209 | $coursefullname = format_string($course->fullname, true, ['context' => $context]); |
e9de4309 | 210 | \core_payment\helper::gateways_modal_requirejs(); |
8ef156b7 | 211 | $attributes = core_payment\helper::gateways_modal_link_params($cost, $instance->currency, 'enrol_fee', |
e88e3b87 | 212 | $instance->id, get_string('purchasedescription', 'enrol_fee', $coursefullname)); |
e9de4309 SR |
213 | |
214 | echo '<div align="center">' . | |
215 | html_writer::tag('button', get_string("sendpaymentbutton", "enrol_paypal"), $attributes) . | |
216 | '</div>'; | |
a580b337 SR |
217 | } |
218 | ||
219 | } | |
220 | ||
221 | return $OUTPUT->box(ob_get_clean()); | |
222 | } | |
223 | ||
224 | /** | |
225 | * Restore instance and map settings. | |
226 | * | |
227 | * @param restore_enrolments_structure_step $step | |
228 | * @param stdClass $data | |
229 | * @param stdClass $course | |
230 | * @param int $oldid | |
231 | */ | |
232 | public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) { | |
233 | global $DB; | |
234 | if ($step->get_task()->get_target() == backup::TARGET_NEW_COURSE) { | |
235 | $merge = false; | |
236 | } else { | |
237 | $merge = array( | |
238 | 'courseid' => $data->courseid, | |
239 | 'enrol' => $this->get_name(), | |
240 | 'roleid' => $data->roleid, | |
241 | 'cost' => $data->cost, | |
242 | 'currency' => $data->currency, | |
243 | ); | |
244 | } | |
245 | if ($merge and $instances = $DB->get_records('enrol', $merge, 'id')) { | |
246 | $instance = reset($instances); | |
247 | $instanceid = $instance->id; | |
248 | } else { | |
249 | $instanceid = $this->add_instance($course, (array) $data); | |
250 | } | |
251 | $step->set_mapping('enrol', $oldid, $instanceid); | |
252 | } | |
253 | ||
254 | /** | |
255 | * Restore user enrolment. | |
256 | * | |
257 | * @param restore_enrolments_structure_step $step | |
258 | * @param stdClass $data | |
259 | * @param stdClass $instance | |
260 | * @param int $oldinstancestatus | |
261 | * @param int $userid | |
262 | */ | |
263 | public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) { | |
264 | $this->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, $data->status); | |
265 | } | |
266 | ||
267 | /** | |
268 | * Return an array of valid options for the status. | |
269 | * | |
270 | * @return array | |
271 | */ | |
272 | protected function get_status_options() { | |
273 | $options = array(ENROL_INSTANCE_ENABLED => get_string('yes'), | |
274 | ENROL_INSTANCE_DISABLED => get_string('no')); | |
275 | return $options; | |
276 | } | |
277 | ||
278 | /** | |
279 | * Return an array of valid options for the roleid. | |
280 | * | |
281 | * @param stdClass $instance | |
282 | * @param context $context | |
283 | * @return array | |
284 | */ | |
285 | protected function get_roleid_options($instance, $context) { | |
286 | if ($instance->id) { | |
287 | $roles = get_default_enrol_roles($context, $instance->roleid); | |
288 | } else { | |
289 | $roles = get_default_enrol_roles($context, $this->get_config('roleid')); | |
290 | } | |
291 | return $roles; | |
292 | } | |
293 | ||
294 | ||
295 | /** | |
296 | * Add elements to the edit instance form. | |
297 | * | |
298 | * @param stdClass $instance | |
299 | * @param MoodleQuickForm $mform | |
300 | * @param context $context | |
301 | * @return bool | |
302 | */ | |
303 | public function edit_instance_form($instance, MoodleQuickForm $mform, $context) { | |
304 | ||
305 | $mform->addElement('text', 'name', get_string('custominstancename', 'enrol')); | |
306 | $mform->setType('name', PARAM_TEXT); | |
307 | ||
308 | $options = $this->get_status_options(); | |
309 | $mform->addElement('select', 'status', get_string('status', 'enrol_fee'), $options); | |
310 | $mform->setDefault('status', $this->get_config('status')); | |
311 | ||
312 | $mform->addElement('text', 'cost', get_string('cost', 'enrol_fee'), array('size' => 4)); | |
313 | $mform->setType('cost', PARAM_RAW); | |
314 | $mform->setDefault('cost', format_float($this->get_config('cost'), 2, true)); | |
315 | ||
316 | $supportedcurrencies = $this->get_possible_currencies(); | |
317 | $mform->addElement('select', 'currency', get_string('currency', 'enrol_fee'), $supportedcurrencies); | |
318 | $mform->setDefault('currency', $this->get_config('currency')); | |
319 | ||
320 | $roles = $this->get_roleid_options($instance, $context); | |
321 | $mform->addElement('select', 'roleid', get_string('assignrole', 'enrol_fee'), $roles); | |
322 | $mform->setDefault('roleid', $this->get_config('roleid')); | |
323 | ||
324 | $options = array('optional' => true, 'defaultunit' => 86400); | |
325 | $mform->addElement('duration', 'enrolperiod', get_string('enrolperiod', 'enrol_fee'), $options); | |
326 | $mform->setDefault('enrolperiod', $this->get_config('enrolperiod')); | |
327 | $mform->addHelpButton('enrolperiod', 'enrolperiod', 'enrol_fee'); | |
328 | ||
329 | $options = array('optional' => true); | |
330 | $mform->addElement('date_time_selector', 'enrolstartdate', get_string('enrolstartdate', 'enrol_fee'), $options); | |
331 | $mform->setDefault('enrolstartdate', 0); | |
332 | $mform->addHelpButton('enrolstartdate', 'enrolstartdate', 'enrol_fee'); | |
333 | ||
334 | $options = array('optional' => true); | |
335 | $mform->addElement('date_time_selector', 'enrolenddate', get_string('enrolenddate', 'enrol_fee'), $options); | |
336 | $mform->setDefault('enrolenddate', 0); | |
337 | $mform->addHelpButton('enrolenddate', 'enrolenddate', 'enrol_fee'); | |
338 | ||
339 | if (enrol_accessing_via_instance($instance)) { | |
340 | $warningtext = get_string('instanceeditselfwarningtext', 'core_enrol'); | |
341 | $mform->addElement('static', 'selfwarn', get_string('instanceeditselfwarning', 'core_enrol'), $warningtext); | |
342 | } | |
343 | } | |
344 | ||
345 | /** | |
346 | * Perform custom validation of the data used to edit the instance. | |
347 | * | |
348 | * @param array $data array of ("fieldname"=>value) of submitted data | |
349 | * @param array $files array of uploaded files "element_name"=>tmp_file_path | |
350 | * @param object $instance The instance loaded from the DB | |
351 | * @param context $context The context of the instance we are editing | |
352 | * @return array of "element_name"=>"error_description" if there are errors, | |
353 | * or an empty array if everything is OK. | |
354 | * @return void | |
355 | */ | |
356 | public function edit_instance_validation($data, $files, $instance, $context) { | |
357 | $errors = array(); | |
358 | ||
359 | if (!empty($data['enrolenddate']) and $data['enrolenddate'] < $data['enrolstartdate']) { | |
360 | $errors['enrolenddate'] = get_string('enrolenddaterror', 'enrol_fee'); | |
361 | } | |
362 | ||
363 | $cost = str_replace(get_string('decsep', 'langconfig'), '.', $data['cost']); | |
364 | if (!is_numeric($cost)) { | |
365 | $errors['cost'] = get_string('costerror', 'enrol_fee'); | |
366 | } | |
367 | ||
368 | $validstatus = array_keys($this->get_status_options()); | |
369 | $validcurrency = array_keys($this->get_possible_currencies()); | |
370 | $validroles = array_keys($this->get_roleid_options($instance, $context)); | |
371 | $tovalidate = array( | |
372 | 'name' => PARAM_TEXT, | |
373 | 'status' => $validstatus, | |
374 | 'currency' => $validcurrency, | |
375 | 'roleid' => $validroles, | |
376 | 'enrolperiod' => PARAM_INT, | |
377 | 'enrolstartdate' => PARAM_INT, | |
378 | 'enrolenddate' => PARAM_INT | |
379 | ); | |
380 | ||
381 | $typeerrors = $this->validate_param_types($data, $tovalidate); | |
382 | $errors = array_merge($errors, $typeerrors); | |
383 | ||
384 | return $errors; | |
385 | } | |
386 | ||
387 | /** | |
388 | * Execute synchronisation. | |
389 | * @param progress_trace $trace | |
390 | * @return int exit code, 0 means ok | |
391 | */ | |
392 | public function sync(progress_trace $trace) { | |
393 | $this->process_expirations($trace); | |
394 | return 0; | |
395 | } | |
396 | ||
397 | /** | |
398 | * Is it possible to delete enrol instance via standard UI? | |
399 | * | |
400 | * @param stdClass $instance | |
401 | * @return bool | |
402 | */ | |
403 | public function can_delete_instance($instance) { | |
404 | $context = context_course::instance($instance->courseid); | |
405 | return has_capability('enrol/fee:config', $context); | |
406 | } | |
407 | ||
408 | /** | |
409 | * Is it possible to hide/show enrol instance via standard UI? | |
410 | * | |
411 | * @param stdClass $instance | |
412 | * @return bool | |
413 | */ | |
414 | public function can_hide_show_instance($instance) { | |
415 | $context = context_course::instance($instance->courseid); | |
416 | return has_capability('enrol/fee:config', $context); | |
417 | } | |
418 | } |