ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç PK\: db/access.phpnu[. /** * Capabilities for fee enrolment plugin. * * @package enrol_fee * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'enrol/fee:config' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'manager' => CAP_ALLOW, ) ), 'enrol/fee:manage' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'manager' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, ) ), 'enrol/fee:unenrol' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'manager' => CAP_ALLOW, ) ), 'enrol/fee:unenrolself' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( ) ), ); PK\db/upgrade.phpnu[. /** * Upgrade steps for Enrolment on payment * * Documentation: {@link https://moodledev.io/docs/guides/upgrade} * * @package enrol_fee * @category upgrade * @copyright Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Execute the plugin upgrade steps from the given old version. * * @param int $oldversion * @return bool */ function xmldb_enrol_fee_upgrade($oldversion) { global $DB; $dbman = $DB->get_manager(); if ($oldversion < 2025011300) { // Move the contents of the 'name' field into 'customchar1', it will be used as a internal description. // Starting from this version the 'name' will be visible to the potential students on the enrolment page. $DB->execute('UPDATE {enrol} SET customchar1 = name, name = ? '. 'WHERE name IS NOT NULL AND name <> ? AND enrol = ?', ['', '', 'fee']); upgrade_plugin_savepoint(true, 2025011300, 'enrol', 'fee'); } // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK\TUUlang/en/enrol_fee.phpnu[. /** * Strings for component 'enrol_fee', language 'en' * * @package enrol_fee * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['assignrole'] = 'Assign role'; $string['cost'] = 'Enrolment fee'; $string['costerror'] = 'The enrolment fee must be a number.'; $string['currency'] = 'Currency'; $string['defaultrole'] = 'Default role assignment'; $string['defaultrole_desc'] = 'Select the role to assign to users after making a payment.'; $string['enrolenddate'] = 'End date'; $string['enrolenddate_help'] = 'If enabled, users can be enrolled until this date only.'; $string['enrolenddaterror'] = 'The enrolment end date cannot be earlier than the start date.'; $string['enrolperiod'] = 'Enrolment duration'; $string['enrolperiod_desc'] = 'Default length of time that the enrolment is valid. If set to zero, the enrolment duration will be unlimited by default.'; $string['enrolperiod_help'] = 'Length of time that the enrolment is valid, starting with the moment the user is enrolled. If disabled, the enrolment duration will be unlimited.'; $string['enrolstartdate'] = 'Start date'; $string['enrolstartdate_help'] = 'If enabled, users can only be enrolled from this date onwards.'; $string['expiredaction'] = 'Enrolment expiry action'; $string['expiredaction_help'] = 'Select the action to be performed when a user\'s enrolment expires. Please note that some user data and settings are deleted when a user is unenrolled.'; $string['fee:config'] = 'Configure enrolment on payment enrol instances'; $string['fee:manage'] = 'Manage enrolled users'; $string['fee:unenrol'] = 'Unenrol users from course'; $string['fee:unenrolself'] = 'Unenrol self from course'; $string['instancedescription'] = 'Instance description'; $string['instancedescription_help'] = 'The description is only shown on the \'Enrolment methods\' page and is not shown to users enrolling in the course.'; $string['nocost'] = 'There is no cost to enrol in this course!'; $string['paymentaccount'] = 'Payment account'; $string['paymentaccount_help'] = 'Enrolment fees will be paid to this account.'; $string['pluginname'] = 'Enrolment on payment'; $string['pluginname_desc'] = 'The enrolment on payment enrolment method allows you to set up courses requiring a payment. If the fee for any course is set to zero, then students are not asked to pay for entry. There is a site-wide fee that you set here as a default for the whole site and then a course setting that you can set for each course individually. The course fee overrides the site fee.'; $string['privacy:metadata'] = 'The enrolment on payment enrolment plugin does not store any personal data.'; $string['purchasedescription'] = 'Enrolment in course {$a}'; $string['sendpaymentbutton'] = 'Select payment type'; $string['status'] = 'Allow enrolment on payment enrolments'; $string['status_desc'] = 'Allow users to make a payment to enrol into a course by default.'; PK\b% pix/icon.svgnu[ PK\Hlib.phpnu[. /** * Fee enrolment plugin. * * This plugin allows you to set up paid courses. * * @package enrol_fee * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ PK\&j version.phpnu[. /** * Fee enrolment plugin version specification. * * @package enrol_fee * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'enrol_fee'; // Full name of the plugin (used for diagnostics). PK\G''classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for enrol_fee. * * @package enrol_fee * @category privacy * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace enrol_fee\privacy; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\approved_userlist; use core_privacy\local\request\transform; use core_privacy\local\request\userlist; use core_privacy\local\request\writer; use core_payment\helper as payment_helper; /** * Privacy Subsystem for enrol_fee implementing null_provider. * * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider, \core_payment\privacy\consumer_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } public static function get_contextid_for_payment(string $paymentarea, int $itemid): ?int { global $DB; $sql = "SELECT ctx.id FROM {enrol} e JOIN {context} ctx ON (e.courseid = ctx.instanceid AND ctx.contextlevel = :contextcourse) WHERE e.id = :enrolid AND e.enrol = :enrolname"; $params = [ 'contextcourse' => CONTEXT_COURSE, 'enrolid' => $itemid, 'enrolname' => 'fee', ]; $contextid = $DB->get_field_sql($sql, $params); return $contextid ?: null; } public static function get_users_in_context(userlist $userlist) { $context = $userlist->get_context(); if ($context instanceof \context_course) { $sql = "SELECT p.userid FROM {payments} p JOIN {enrol} e ON (p.component = :component AND p.itemid = e.id) WHERE e.courseid = :courseid"; $params = [ 'component' => 'enrol_fee', 'courseid' => $context->instanceid, ]; $userlist->add_from_sql('userid', $sql, $params); } else if ($context instanceof \context_system) { // If context is system, then the enrolment belongs to a deleted enrolment. $sql = "SELECT p.userid FROM {payments} p LEFT JOIN {enrol} e ON p.itemid = e.id WHERE p.component = :component AND e.id IS NULL"; $params = [ 'component' => 'enrol_fee', ]; $userlist->add_from_sql('userid', $sql, $params); } } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { global $DB; $subcontext = [ get_string('pluginname', 'enrol_fee'), ]; foreach ($contextlist as $context) { if (!$context instanceof \context_course) { continue; } $feeplugins = $DB->get_records('enrol', ['courseid' => $context->instanceid, 'enrol' => 'fee']); foreach ($feeplugins as $feeplugin) { \core_payment\privacy\provider::export_payment_data_for_user_in_context( $context, $subcontext, $contextlist->get_user()->id, 'enrol_fee', 'fee', $feeplugin->id ); } } if (in_array(SYSCONTEXTID, $contextlist->get_contextids())) { // Orphaned payments. $sql = "SELECT p.* FROM {payments} p LEFT JOIN {enrol} e ON p.itemid = e.id WHERE p.userid = :userid AND p.component = :component AND e.id IS NULL"; $params = [ 'component' => 'enrol_fee', 'userid' => $contextlist->get_user()->id, ]; $orphanedpayments = $DB->get_recordset_sql($sql, $params); foreach ($orphanedpayments as $payment) { \core_payment\privacy\provider::export_payment_data_for_user_in_context( \context_system::instance(), $subcontext, $payment->userid, $payment->component, $payment->paymentarea, $payment->itemid ); } $orphanedpayments->close(); } } /** * Delete all data for all users in the specified context. * * @param \context $context The specific context to delete data for. */ public static function delete_data_for_all_users_in_context(\context $context) { if ($context instanceof \context_course) { $sql = "SELECT p.id FROM {payments} p JOIN {enrol} e ON (p.component = :component AND p.itemid = e.id) WHERE e.courseid = :courseid"; $params = [ 'component' => 'enrol_fee', 'courseid' => $context->instanceid, ]; \core_payment\privacy\provider::delete_data_for_payment_sql($sql, $params); } else if ($context instanceof \context_system) { // If context is system, then the enrolment belongs to a deleted enrolment. $sql = "SELECT p.id FROM {payments} p LEFT JOIN {enrol} e ON p.itemid = e.id WHERE p.component = :component AND e.id IS NULL"; $params = [ 'component' => 'enrol_fee', ]; \core_payment\privacy\provider::delete_data_for_payment_sql($sql, $params); } } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { global $DB; if (empty($contextlist->count())) { return; } $contexts = $contextlist->get_contexts(); $courseids = []; foreach ($contexts as $context) { if ($context instanceof \context_course) { $courseids[] = $context->instanceid; } } [$insql, $inparams] = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED); $sql = "SELECT p.id FROM {payments} p JOIN {enrol} e ON (p.component = :component AND p.itemid = e.id) WHERE p.userid = :userid AND e.courseid $insql"; $params = $inparams + [ 'component' => 'enrol_fee', 'userid' => $contextlist->get_user()->id, ]; \core_payment\privacy\provider::delete_data_for_payment_sql($sql, $params); if (in_array(SYSCONTEXTID, $contextlist->get_contextids())) { // Orphaned payments. $sql = "SELECT p.id FROM {payments} p LEFT JOIN {enrol} e ON p.itemid = e.id WHERE p.component = :component AND p.userid = :userid AND e.id IS NULL"; $params = [ 'component' => 'enrol_fee', 'userid' => $contextlist->get_user()->id, ]; \core_payment\privacy\provider::delete_data_for_payment_sql($sql, $params); } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { global $DB; $context = $userlist->get_context(); if ($context instanceof \context_course) { [$usersql, $userparams] = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); $sql = "SELECT p.id FROM {payments} p JOIN {enrol} e ON (p.component = :component AND p.itemid = e.id) WHERE e.courseid = :courseid AND p.userid $usersql"; $params = $userparams + [ 'component' => 'enrol_fee', 'courseid' => $context->instanceid, ]; \core_payment\privacy\provider::delete_data_for_payment_sql($sql, $params); } else if ($context instanceof \context_system) { // Orphaned payments. [$usersql, $userparams] = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); $sql = "SELECT p.id FROM {payments} p LEFT JOIN {enrol} e ON p.itemid = e.id WHERE p.component = :component AND p.userid $usersql AND e.id IS NULL"; $params = $userparams + [ 'component' => 'enrol_fee', ]; \core_payment\privacy\provider::delete_data_for_payment_sql($sql, $params); } } } PK\=H7EEclasses/plugin.phpnu[. /** * Fee enrolment plugin. * * This plugin allows you to set up paid courses. * * @package enrol_fee * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use core\output\single_button; use core_enrol\output\enrol_page; /** * Fee enrolment plugin implementation. * * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class enrol_fee_plugin extends enrol_plugin { /** * How this enrolment method should be displayed on the "Enrolment methods" page * * @param stdClass $instance * @return string */ public function get_instance_name_for_management_page(stdClass $instance): string { $result = $this->get_instance_name($instance); if (strlen((string)$instance->customchar1)) { $context = context_course::instance($instance->courseid); $result .= html_writer::empty_tag('br') . html_writer::tag('em', format_string($instance->customchar1, true, ['context' => $context])); } return $result; } /** * Returns the list of currencies that the payment subsystem supports and therefore we can work with. * * @return array[currencycode => currencyname] */ public function get_possible_currencies(): array { $codes = \core_payment\helper::get_supported_currencies(); $currencies = []; foreach ($codes as $c) { $currencies[$c] = new lang_string($c, 'core_currencies'); } uasort($currencies, function($a, $b) { return strcmp($a, $b); }); return $currencies; } /** * Returns optional enrolment information icons. * * This is used in course list for quick overview of enrolment options. * * We are not using single instance parameter because sometimes * we might want to prevent icon repetition when multiple instances * of one type exist. One instance may also produce several icons. * * @param array $instances all enrol instances of this type in one course * @return array of pix_icon */ public function get_info_icons(array $instances) { $found = false; foreach ($instances as $instance) { if ($instance->enrolstartdate != 0 && $instance->enrolstartdate > time()) { continue; } if ($instance->enrolenddate != 0 && $instance->enrolenddate < time()) { continue; } $found = true; break; } if ($found) { return array(new pix_icon('icon', get_string('pluginname', 'enrol_fee'), 'enrol_fee')); } return array(); } public function roles_protected() { // Users with role assign cap may tweak the roles later. return false; } public function allow_unenrol(stdClass $instance) { // Users with unenrol cap may unenrol other users manually - requires enrol/fee:unenrol. return true; } public function allow_manage(stdClass $instance) { // Users with manage cap may tweak period and status - requires enrol/fee:manage. return true; } public function show_enrolme_link(stdClass $instance) { return ($instance->status == ENROL_INSTANCE_ENABLED); } /** * Returns true if the user can add a new instance in this course. * @param int $courseid * @return boolean */ public function can_add_instance($courseid) { $context = context_course::instance($courseid, MUST_EXIST); if (empty(\core_payment\helper::get_supported_currencies())) { return false; } if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/fee:config', $context)) { return false; } // Multiple instances supported - different cost for different roles. return true; } /** * We are a good plugin and don't invent our own UI/validation code path. * * @return boolean */ public function use_standard_editing_ui() { return true; } /** * Add new instance of enrol plugin. * @param object $course * @param array $fields instance fields * @return int id of new instance, null if can not be created */ public function add_instance($course, ?array $fields = null) { if ($fields && !empty($fields['cost'])) { $fields['cost'] = unformat_float($fields['cost']); } return parent::add_instance($course, $fields); } /** * Update instance of enrol plugin. * @param stdClass $instance * @param stdClass $data modified instance fields * @return boolean */ public function update_instance($instance, $data) { if ($data) { $data->cost = unformat_float($data->cost); } return parent::update_instance($instance, $data); } #[\Override] public function enrol_page_hook(stdClass $instance) { global $USER, $OUTPUT, $DB, $PAGE; if ($DB->record_exists('user_enrolments', array('userid' => $USER->id, 'enrolid' => $instance->id))) { return ''; } if ($instance->enrolstartdate != 0 && $instance->enrolstartdate > time()) { return ''; } if ($instance->enrolenddate != 0 && $instance->enrolenddate < time()) { return ''; } $course = $DB->get_record('course', array('id' => $instance->courseid)); $context = context_course::instance($course->id); if ( (float) $instance->cost <= 0 ) { $cost = (float) $this->get_config('cost'); } else { $cost = (float) $instance->cost; } $name = !empty($instance->name) ? format_string($instance->name, true, ['context' => $context]) : get_string('paymentrequired'); if (abs($cost) < 0.01) { // No cost, other enrolment methods (instances) should be used. $notification = new \core\output\notification(get_string('nocost', 'enrol_fee'), 'error', false); $notification->set_extra_classes(['mb-0']); $enrolpage = new enrol_page( instance: $instance, header: $name, body: $OUTPUT->render($notification)); return $OUTPUT->render($enrolpage); } else { if (isguestuser() || !isloggedin()) { $button = new single_button(new moodle_url(get_login_url()), get_string('loginsite'), 'get', single_button::BUTTON_PRIMARY); } else { $PAGE->requires->js_call_amd('core_payment/gateways_modal', 'init'); $button = new single_button( $PAGE->url, get_string('sendpaymentbutton', 'enrol_fee'), 'post', single_button::BUTTON_PRIMARY, [ 'data-action' => 'core_payment/triggerPayment', 'data-component' => 'enrol_fee', 'data-paymentarea' => 'fee', 'data-itemid' => $instance->id, 'data-cost' => $cost, 'data-successurl' => \enrol_fee\payment\service_provider::get_success_url('fee', $instance->id)->out(false), 'data-description' => get_string('purchasedescription', 'enrol_fee', format_string($course->fullname, true, ['context' => $context])), ]); } $body = $OUTPUT->render_from_template('enrol_fee/enrol_page', [ 'cost' => \core_payment\helper::get_cost_as_string($cost, $instance->currency), ]); $enrolpage = new enrol_page( instance: $instance, header: $name, body: $body, buttons: [$button]); return $OUTPUT->render($enrolpage); } } /** * Restore instance and map settings. * * @param restore_enrolments_structure_step $step * @param stdClass $data * @param stdClass $course * @param int $oldid */ public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) { global $DB; if ($step->get_task()->get_target() == backup::TARGET_NEW_COURSE) { $merge = false; } else { $merge = array( 'courseid' => $data->courseid, 'enrol' => $this->get_name(), 'roleid' => $data->roleid, 'cost' => $data->cost, 'currency' => $data->currency, ); } if ($merge and $instances = $DB->get_records('enrol', $merge, 'id')) { $instance = reset($instances); $instanceid = $instance->id; } else { $instanceid = $this->add_instance($course, (array) $data); } $step->set_mapping('enrol', $oldid, $instanceid); } /** * Restore user enrolment. * * @param restore_enrolments_structure_step $step * @param stdClass $data * @param stdClass $instance * @param int $oldinstancestatus * @param int $userid */ public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) { $this->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, $data->status); } /** * Return an array of valid options for the status. * * @return array */ protected function get_status_options() { $options = array(ENROL_INSTANCE_ENABLED => get_string('yes'), ENROL_INSTANCE_DISABLED => get_string('no')); return $options; } /** * Return an array of valid options for the roleid. * * @param stdClass $instance * @param context $context * @return array */ protected function get_roleid_options($instance, $context) { if ($instance->id) { $roles = get_default_enrol_roles($context, $instance->roleid); } else { $roles = get_default_enrol_roles($context, $this->get_config('roleid')); } return $roles; } /** * Add elements to the edit instance form. * * @param stdClass $instance * @param MoodleQuickForm $mform * @param context $context */ public function edit_instance_form($instance, MoodleQuickForm $mform, $context) { $mform->addElement('text', 'name', get_string('custominstancename', 'enrol')); $mform->setType('name', PARAM_TEXT); $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); $mform->addElement('text', 'customchar1', get_string('instancedescription', 'enrol_fee')); $mform->setType('customchar1', PARAM_TEXT); $mform->addHelpButton('customchar1', 'instancedescription', 'enrol_fee'); $mform->addRule('customchar1', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); $options = $this->get_status_options(); $mform->addElement('select', 'status', get_string('status', 'enrol_fee'), $options); $mform->setDefault('status', $this->get_config('status')); $accounts = \core_payment\helper::get_payment_accounts_menu($context); if ($accounts) { $accounts = ((count($accounts) > 1) ? ['' => ''] : []) + $accounts; $mform->addElement('select', 'customint1', get_string('paymentaccount', 'payment'), $accounts); } else { $mform->addElement('static', 'customint1_text', get_string('paymentaccount', 'payment'), html_writer::span(get_string('noaccountsavilable', 'payment'), 'alert alert-danger')); $mform->addElement('hidden', 'customint1'); $mform->setType('customint1', PARAM_INT); } $mform->addHelpButton('customint1', 'paymentaccount', 'enrol_fee'); $mform->addElement('text', 'cost', get_string('cost', 'enrol_fee'), array('size' => 4)); $mform->setType('cost', PARAM_RAW); $mform->setDefault('cost', format_float($this->get_config('cost'), 2, true)); $supportedcurrencies = $this->get_possible_currencies(); $mform->addElement('select', 'currency', get_string('currency', 'enrol_fee'), $supportedcurrencies); $mform->setDefault('currency', $this->get_config('currency')); $roles = $this->get_roleid_options($instance, $context); $mform->addElement('select', 'roleid', get_string('assignrole', 'enrol_fee'), $roles); $mform->setDefault('roleid', $this->get_config('roleid')); $options = array('optional' => true, 'defaultunit' => 86400); $mform->addElement('duration', 'enrolperiod', get_string('enrolperiod', 'enrol_fee'), $options); $mform->setDefault('enrolperiod', $this->get_config('enrolperiod')); $mform->addHelpButton('enrolperiod', 'enrolperiod', 'enrol_fee'); $options = array('optional' => true); $mform->addElement('date_time_selector', 'enrolstartdate', get_string('enrolstartdate', 'enrol_fee'), $options); $mform->setDefault('enrolstartdate', 0); $mform->addHelpButton('enrolstartdate', 'enrolstartdate', 'enrol_fee'); $options = array('optional' => true); $mform->addElement('date_time_selector', 'enrolenddate', get_string('enrolenddate', 'enrol_fee'), $options); $mform->setDefault('enrolenddate', 0); $mform->addHelpButton('enrolenddate', 'enrolenddate', 'enrol_fee'); if (enrol_accessing_via_instance($instance)) { $warningtext = get_string('instanceeditselfwarningtext', 'core_enrol'); $mform->addElement('static', 'selfwarn', get_string('instanceeditselfwarning', 'core_enrol'), $warningtext); } } /** * Perform custom validation of the data used to edit the instance. * * @param array $data array of ("fieldname"=>value) of submitted data * @param array $files array of uploaded files "element_name"=>tmp_file_path * @param object $instance The instance loaded from the DB * @param context $context The context of the instance we are editing * @return array of "element_name"=>"error_description" if there are errors, * or an empty array if everything is OK. */ public function edit_instance_validation($data, $files, $instance, $context) { $errors = array(); if (!empty($data['enrolenddate']) and $data['enrolenddate'] < $data['enrolstartdate']) { $errors['enrolenddate'] = get_string('enrolenddaterror', 'enrol_fee'); } $cost = str_replace(get_string('decsep', 'langconfig'), '.', $data['cost']); if (!is_numeric($cost)) { $errors['cost'] = get_string('costerror', 'enrol_fee'); } $validstatus = array_keys($this->get_status_options()); $validcurrency = array_keys($this->get_possible_currencies()); $validroles = array_keys($this->get_roleid_options($instance, $context)); $tovalidate = array( 'name' => PARAM_TEXT, 'status' => $validstatus, 'currency' => $validcurrency, 'roleid' => $validroles, 'enrolperiod' => PARAM_INT, 'enrolstartdate' => PARAM_INT, 'enrolenddate' => PARAM_INT ); $typeerrors = $this->validate_param_types($data, $tovalidate); $errors = array_merge($errors, $typeerrors); if ($data['status'] == ENROL_INSTANCE_ENABLED && (!$data['customint1'] || !array_key_exists($data['customint1'], \core_payment\helper::get_payment_accounts_menu($context)))) { $errors['status'] = 'Enrolments can not be enabled without specifying the payment account'; } return $errors; } /** * Execute synchronisation. * @param progress_trace $trace * @return int exit code, 0 means ok */ public function sync(progress_trace $trace) { $this->process_expirations($trace); return 0; } /** * Is it possible to delete enrol instance via standard UI? * * @param stdClass $instance * @return bool */ public function can_delete_instance($instance) { $context = context_course::instance($instance->courseid); return has_capability('enrol/fee:config', $context); } /** * Is it possible to hide/show enrol instance via standard UI? * * @param stdClass $instance * @return bool */ public function can_hide_show_instance($instance) { $context = context_course::instance($instance->courseid); return has_capability('enrol/fee:config', $context); } } PK\2+ $classes/payment/service_provider.phpnu[. /** * Payment subsystem callback implementation for enrol_fee. * * @package enrol_fee * @category payment * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace enrol_fee\payment; /** * Payment subsystem callback implementation for enrol_fee. * * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class service_provider implements \core_payment\local\callback\service_provider { /** * Callback function that returns the enrolment cost and the accountid * for the course that $instanceid enrolment instance belongs to. * * @param string $paymentarea Payment area * @param int $instanceid The enrolment instance id * @return \core_payment\local\entities\payable */ public static function get_payable(string $paymentarea, int $instanceid): \core_payment\local\entities\payable { global $DB; $instance = $DB->get_record('enrol', ['enrol' => 'fee', 'id' => $instanceid], '*', MUST_EXIST); return new \core_payment\local\entities\payable($instance->cost, $instance->currency, $instance->customint1); } /** * Callback function that returns the URL of the page the user should be redirected to in the case of a successful payment. * * @param string $paymentarea Payment area * @param int $instanceid The enrolment instance id * @return \moodle_url */ public static function get_success_url(string $paymentarea, int $instanceid): \moodle_url { global $DB; $courseid = $DB->get_field('enrol', 'courseid', ['enrol' => 'fee', 'id' => $instanceid], MUST_EXIST); return new \moodle_url('/course/view.php', ['id' => $courseid]); } /** * Callback function that delivers what the user paid for to them. * * @param string $paymentarea * @param int $instanceid The enrolment instance id * @param int $paymentid payment id as inserted into the 'payments' table, if needed for reference * @param int $userid The userid the order is going to deliver to * @return bool Whether successful or not */ public static function deliver_order(string $paymentarea, int $instanceid, int $paymentid, int $userid): bool { global $DB; $instance = $DB->get_record('enrol', ['enrol' => 'fee', 'id' => $instanceid], '*', MUST_EXIST); $plugin = enrol_get_plugin('fee'); if ($instance->enrolperiod) { $timestart = time(); $timeend = $timestart + $instance->enrolperiod; } else { $timestart = 0; $timeend = 0; } $plugin->enrol_user($instance, $userid, $instance->roleid, $timestart, $timeend); return true; } } PK\/Vtemplates/enrol_page.mustachenu[{{! 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 . }} {{! @template enrol_fee/enrol_page Contents of the enrolment widget on the course enrolment page Example context (json): { "cost": "10.00", "currency": "USD" } }} {{#str}} labelvalue, core, {"label": {{#quote}}{{#str}} cost {{/str}}{{/quote}}, "value": {{#quote}}{{cost}}{{/quote}} }{{/str}} PK\tfXX'tests/payment/service_provider_test.phpnu[. /** * Unit tests for the enrol_fee's payment subsystem callback implementation. * * @package enrol_fee * @category test * @copyright 2021 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace enrol_fee\payment; /** * Unit tests for the enrol_fee's payment subsystem callback implementation. * * @coversDefaultClass \enrol_fee\payment\service_provider */ final class service_provider_test extends \advanced_testcase { /** * Test for service_provider::get_payable(). * * @covers ::get_payable */ public function test_get_payable(): void { global $DB; $this->resetAfterTest(); $studentrole = $DB->get_record('role', ['shortname' => 'student']); $feeplugin = enrol_get_plugin('fee'); $generator = $this->getDataGenerator(); $account = $generator->get_plugin_generator('core_payment')->create_payment_account(['gateways' => 'paypal']); $course = $generator->create_course(); $data = [ 'courseid' => $course->id, 'customint1' => $account->get('id'), 'cost' => 250, 'currency' => 'USD', 'roleid' => $studentrole->id, ]; $id = $feeplugin->add_instance($course, $data); $payable = service_provider::get_payable('fee', $id); $this->assertEquals($account->get('id'), $payable->get_account_id()); $this->assertEquals(250, $payable->get_amount()); $this->assertEquals('USD', $payable->get_currency()); } /** * Test for service_provider::get_success_url(). * * @covers ::get_success_url */ public function test_get_success_url(): void { global $CFG, $DB; $this->resetAfterTest(); $studentrole = $DB->get_record('role', ['shortname' => 'student']); $feeplugin = enrol_get_plugin('fee'); $generator = $this->getDataGenerator(); $account = $generator->get_plugin_generator('core_payment')->create_payment_account(['gateways' => 'paypal']); $course = $generator->create_course(); $data = [ 'courseid' => $course->id, 'customint1' => $account->get('id'), 'cost' => 250, 'currency' => 'USD', 'roleid' => $studentrole->id, ]; $id = $feeplugin->add_instance($course, $data); $successurl = service_provider::get_success_url('fee', $id); $this->assertEquals( $CFG->wwwroot . '/course/view.php?id=' . $course->id, $successurl->out(false) ); } /** * Test for service_provider::deliver_order(). * * @covers ::deliver_order */ public function test_deliver_order(): void { global $DB; $this->resetAfterTest(); $studentrole = $DB->get_record('role', ['shortname' => 'student']); $feeplugin = enrol_get_plugin('fee'); $generator = $this->getDataGenerator(); $account = $generator->get_plugin_generator('core_payment')->create_payment_account(['gateways' => 'paypal']); $course = $generator->create_course(); $context = \context_course::instance($course->id); $user = $generator->create_user(); $data = [ 'courseid' => $course->id, 'customint1' => $account->get('id'), 'cost' => 250, 'currency' => 'USD', 'roleid' => $studentrole->id, ]; $id = $feeplugin->add_instance($course, $data); $paymentid = $generator->get_plugin_generator('core_payment')->create_payment([ 'accountid' => $account->get('id'), 'amount' => 10, 'userid' => $user->id ]); service_provider::deliver_order('fee', $id, $paymentid, $user->id); $this->assertTrue(is_enrolled($context, $user)); $this->assertTrue(user_has_role_assignment($user->id, $studentrole->id, $context->id)); } } PK\ tests/behat/fee.featurenu[@enrol @enrol_fee Feature: Signing up for a course with a fee enrolment method Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | | manager1 | Manager | 1 | manager1@example.com | And the following "courses" exist: | fullname | shortname | format | summary | | Course 1 | C1 | topics | | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | manager1 | C1 | manager | And the following "core_payment > payment accounts" exist: | name | gateways | | Account1 | paypal | And I log in as "admin" And I navigate to "Plugins > Enrolments > Manage enrol plugins" in site administration And I click on "Enable" "link" in the "Enrolment on payment" "table_row" And I log out And I log in as "manager1" And I am on the "Course 1" "enrolment methods" page And I select "Enrolment on payment" from the "Add method" singleselect And I set the following fields to these values: | Payment account | Account1 | | Enrolment fee | 123.45 | | Currency | Euro | And I press "Add method" And I log out @javascript Scenario: Student can see the payment prompt on the course enrolment page When I log in as "student1" And I am on course index And I follow "Course 1" Then I should see "This course requires a payment for entry" And I should see "123.45" And I press "Select payment type" And I should see "PayPal" in the "Select payment type" "dialogue" And I click on "Cancel" "button" in the "Select payment type" "dialogue" Scenario: Guest can see the login prompt on the course enrolment page When I log in as "guest" And I am on course index And I follow "Course 1" Then I should see "This course requires a payment for entry" And I should see "123.45" And I should see "Log in to the site" @javascript Scenario: Setting a custom instance name for the fee enrolment method And I log in as "manager1" And I am on the "Course 1" "enrolment methods" page And I click on "Edit" "link" in the "Enrolment on payment" "table_row" And I set the following fields to these values: | Custom instance name | Lifetime access | | Instance description | Only for teachers | And I press "Save changes" And I should see "Lifetime access" And I should see "Only for teachers" And I should not see "Enrolment on payment" in the "Lifetime access" "table_row" And I log out When I log in as "student1" And I am on course index And I follow "Course 1" Then I should not see "This course requires a payment for entry" Then I should see "Lifetime access" Then I should not see "Only for teachers" PK\ڲ settings.phpnu[. /** * Settings for the Fee enrolment plugin * * @package enrol_fee * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); if ($ADMIN->fulltree) { $currencies = enrol_get_plugin('fee')->get_possible_currencies(); if (empty($currencies)) { $notify = new \core\output\notification( get_string('nocurrencysupported', 'core_payment'), \core\output\notification::NOTIFY_WARNING ); $settings->add(new admin_setting_heading('enrol_fee_nocurrency', '', $OUTPUT->render($notify))); } $settings->add(new admin_setting_heading('enrol_fee_settings', '', get_string('pluginname_desc', 'enrol_fee'))); // Note: let's reuse the ext sync constants and strings here, internally it is very similar, // it describes what should happen when users are not supposed to be enrolled any more. $options = array( ENROL_EXT_REMOVED_KEEP => get_string('extremovedkeep', 'enrol'), ENROL_EXT_REMOVED_SUSPENDNOROLES => get_string('extremovedsuspendnoroles', 'enrol'), ENROL_EXT_REMOVED_UNENROL => get_string('extremovedunenrol', 'enrol'), ); $settings->add(new admin_setting_configselect( 'enrol_fee/expiredaction', get_string('expiredaction', 'enrol_fee'), get_string('expiredaction_help', 'enrol_fee'), ENROL_EXT_REMOVED_SUSPENDNOROLES, $options)); $settings->add(new admin_setting_heading('enrol_fee_defaults', get_string('enrolinstancedefaults', 'admin'), get_string('enrolinstancedefaults_desc', 'admin'))); $options = array(ENROL_INSTANCE_ENABLED => get_string('yes'), ENROL_INSTANCE_DISABLED => get_string('no')); $settings->add(new admin_setting_configselect('enrol_fee/status', get_string('status', 'enrol_fee'), get_string('status_desc', 'enrol_fee'), ENROL_INSTANCE_DISABLED, $options)); if (!empty($currencies)) { $settings->add(new admin_setting_configtext('enrol_fee/cost', get_string('cost', 'enrol_fee'), '', 0, PARAM_FLOAT, 4)); $settings->add(new admin_setting_configselect('enrol_fee/currency', get_string('currency', 'enrol_fee'), '', 'USD', $currencies)); } if (!during_initial_install()) { $options = get_default_enrol_roles(context_system::instance()); $student = get_archetype_roles('student'); $student = reset($student); $settings->add(new admin_setting_configselect('enrol_fee/roleid', get_string('defaultrole', 'enrol_fee'), get_string('defaultrole_desc', 'enrol_fee'), $student->id ?? null, $options)); } $settings->add(new admin_setting_configduration('enrol_fee/enrolperiod', get_string('enrolperiod', 'enrol_fee'), get_string('enrolperiod_desc', 'enrol_fee'), 0)); } PK\: db/access.phpnu[PK\db/upgrade.phpnu[PK\TUUWlang/en/enrol_fee.phpnu[PK\b% pix/icon.svgnu[PK\HE0lib.phpnu[PK\&j 4version.phpnu[PK\G''9classes/privacy/provider.phpnu[PK\=H7EE`classes/plugin.phpnu[PK\2+ $classes/payment/service_provider.phpnu[PK\/Vtemplates/enrol_page.mustachenu[PK\tfXX'Ctests/payment/service_provider_test.phpnu[PK\ tests/behat/fee.featurenu[PK\ڲ &settings.phpnu[PK Gs