ÿØÿà 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,B\ I.,,db/services.phpnu[. /** * Auth email webservice definitions. * * @package auth_email * @copyright 2016 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $functions = array( 'auth_email_get_signup_settings' => array( 'classname' => 'auth_email_external', 'methodname' => 'get_signup_settings', 'description' => 'Get the signup required settings and profile fields.', 'type' => 'read', 'ajax' => true, 'loginrequired' => false, ), 'auth_email_signup_user' => array( 'classname' => 'auth_email_external', 'methodname' => 'signup_user', 'description' => 'Adds a new user (pendingto be confirmed) in the site.', 'type' => 'write', 'ajax' => true, 'loginrequired' => false, ), ); PK,B\hdddb/upgrade.phpnu[. /** * Upgrade code for email message processor * * @package message_email * @copyright 2008 Luis Rodrigues * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ /** * Upgrade code for the email message processor * * @param int $oldversion The version that we are upgrading from */ function xmldb_message_email_upgrade($oldversion) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK,B\sEZeDDlang/en/auth_email.phpnu[. /** * Strings for component 'auth_email', language 'en'. * * @package auth_email * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['auth_emaildescription'] = '

Email-based self-registration enables a user to create their own account via a \'Create new account\' button on the login page. The user then receives an email containing a secure link to a page where they can confirm their account. Future logins just check the username and password against the stored values in the Moodle database.

Note: In addition to enabling the plugin, email-based self-registration must also be selected from the self registration drop-down menu on the \'Manage authentication\' page.

'; $string['auth_emailnoemail'] = 'Tried to send you an email but failed!'; $string['auth_emailrecaptcha'] = 'Adds a visual/audio confirmation form element to the sign-up page for email self-registering users. This protects your site against spammers and contributes to a worthwhile cause. See https://www.google.com/recaptcha for more details.'; $string['auth_emailrecaptcha_key'] = 'Enable reCAPTCHA element'; $string['auth_emailsettings'] = 'Settings'; $string['pluginname'] = 'Email-based self-registration'; $string['privacy:metadata'] = 'The Email-based self-registration authentication plugin does not store any personal data.'; PK,B\Z< version.phpnu[. /** * Email processor version information * * @package message_email * @copyright 2008 Luis Rodrigues * @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 = 'message_email'; // Full name of the plugin (used for diagnostics) PK,B\AFAFclasses/external.phpnu[. /** * Auth e-mail external API * * @package auth_email * @category external * @copyright 2016 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.2 */ use core_external\external_api; use core_external\external_format_value; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; defined('MOODLE_INTERNAL') || die; require_once($CFG->libdir . '/authlib.php'); require_once($CFG->dirroot . '/user/editlib.php'); require_once($CFG->dirroot . '/user/profile/lib.php'); /** * Auth e-mail external functions * * @package auth_email * @category external * @copyright 2016 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.2 */ class auth_email_external extends external_api { /** * Check if registration is enabled in this site. * * @throws moodle_exception * @since Moodle 3.2 */ protected static function check_signup_enabled() { global $CFG; if (empty($CFG->registerauth) or $CFG->registerauth != 'email') { throw new moodle_exception('registrationdisabled', 'error'); } } /** * Describes the parameters for get_signup_settings. * * @return external_function_parameters * @since Moodle 3.2 */ public static function get_signup_settings_parameters() { return new external_function_parameters(array()); } /** * Get the signup required settings and profile fields. * * @return array settings and possible warnings * @since Moodle 3.2 * @throws moodle_exception */ public static function get_signup_settings() { global $CFG, $PAGE; $context = context_system::instance(); // We need this to make work the format text functions. $PAGE->set_context($context); self::check_signup_enabled(); $result = array(); $result['namefields'] = useredit_get_required_name_fields(); if (!empty($CFG->passwordpolicy)) { $result['passwordpolicy'] = print_password_policy(); } $manager = new \core_privacy\local\sitepolicy\manager(); if ($sitepolicy = $manager->get_embed_url()) { $result['sitepolicy'] = $sitepolicy->out(false); } if (!empty($CFG->sitepolicyhandler)) { $result['sitepolicyhandler'] = $CFG->sitepolicyhandler; } if (!empty($CFG->defaultcity)) { $result['defaultcity'] = $CFG->defaultcity; } if (!empty($CFG->country)) { $result['country'] = $CFG->country; } $result['extendedusernamechars'] = !empty($CFG->extendedusernamechars); if ($fields = profile_get_signup_fields()) { $result['profilefields'] = array(); foreach ($fields as $field) { $fielddata = $field->object->get_field_config_for_external(); $fielddata['categoryname'] = \core_external\util::format_string($field->categoryname, $context->id); $fielddata['name'] = \core_external\util::format_string($fielddata['name'], $context->id); list($fielddata['defaultdata'], $fielddata['defaultdataformat']) = \core_external\util::format_text($fielddata['defaultdata'], $fielddata['defaultdataformat'], $context->id); $result['profilefields'][] = $fielddata; } } if (signup_captcha_enabled()) { // With reCAPTCHA v2 the captcha will be rendered by the mobile client using just the publickey. $result['recaptchapublickey'] = $CFG->recaptchapublickey; } $result['warnings'] = array(); return $result; } /** * Describes the get_signup_settings return value. * * @return external_single_structure * @since Moodle 3.2 */ public static function get_signup_settings_returns() { return new external_single_structure( [ 'namefields' => new external_multiple_structure( new external_value(PARAM_NOTAGS, 'The order of the name fields') ), 'passwordpolicy' => new external_value(PARAM_RAW, 'Password policy', VALUE_OPTIONAL), 'sitepolicy' => new external_value(PARAM_RAW, 'Site policy', VALUE_OPTIONAL), 'sitepolicyhandler' => new external_value(PARAM_PLUGIN, 'Site policy handler', VALUE_OPTIONAL), 'defaultcity' => new external_value(PARAM_NOTAGS, 'Default city', VALUE_OPTIONAL), 'country' => new external_value(PARAM_ALPHA, 'Default country', VALUE_OPTIONAL), 'extendedusernamechars' => new external_value( PARAM_BOOL, 'Extended characters in usernames or not', VALUE_OPTIONAL ), 'profilefields' => new external_multiple_structure( new external_single_structure( [ 'id' => new external_value(PARAM_INT, 'Profile field id', VALUE_OPTIONAL), 'shortname' => new external_value(PARAM_ALPHANUMEXT, 'Profile field shortname', VALUE_OPTIONAL), 'name' => new external_value(PARAM_RAW, 'Profield field name', VALUE_OPTIONAL), 'datatype' => new external_value(PARAM_ALPHANUMEXT, 'Profield field datatype', VALUE_OPTIONAL), 'description' => new external_value(PARAM_RAW, 'Profield field description', VALUE_OPTIONAL), 'descriptionformat' => new external_format_value('description'), 'categoryid' => new external_value(PARAM_INT, 'Profield field category id', VALUE_OPTIONAL), 'categoryname' => new external_value(PARAM_RAW, 'Profield field category name', VALUE_OPTIONAL), 'sortorder' => new external_value(PARAM_INT, 'Profield field sort order', VALUE_OPTIONAL), 'required' => new external_value(PARAM_INT, 'Profield field required', VALUE_OPTIONAL), 'locked' => new external_value(PARAM_INT, 'Profield field locked', VALUE_OPTIONAL), 'visible' => new external_value(PARAM_INT, 'Profield field visible', VALUE_OPTIONAL), 'forceunique' => new external_value(PARAM_INT, 'Profield field unique', VALUE_OPTIONAL), 'signup' => new external_value(PARAM_INT, 'Profield field in signup form', VALUE_OPTIONAL), 'defaultdata' => new external_value(PARAM_RAW, 'Profield field default data', VALUE_OPTIONAL), 'defaultdataformat' => new external_format_value('defaultdata'), 'param1' => new external_value(PARAM_RAW, 'Profield field settings', VALUE_OPTIONAL), 'param2' => new external_value(PARAM_RAW, 'Profield field settings', VALUE_OPTIONAL), 'param3' => new external_value(PARAM_RAW, 'Profield field settings', VALUE_OPTIONAL), 'param4' => new external_value(PARAM_RAW, 'Profield field settings', VALUE_OPTIONAL), 'param5' => new external_value(PARAM_RAW, 'Profield field settings', VALUE_OPTIONAL), ] ), 'Required profile fields', VALUE_OPTIONAL ), 'recaptchapublickey' => new external_value(PARAM_RAW, 'Recaptcha public key', VALUE_OPTIONAL), 'recaptchachallengehash' => new external_value(PARAM_RAW, 'Recaptcha challenge hash', VALUE_OPTIONAL), 'recaptchachallengeimage' => new external_value(PARAM_URL, 'Recaptcha challenge noscript image', VALUE_OPTIONAL), 'recaptchachallengejs' => new external_value(PARAM_URL, 'Recaptcha challenge js url', VALUE_OPTIONAL), 'warnings' => new external_warnings(), ] ); } /** * Describes the parameters for signup_user. * * @return external_function_parameters * @since Moodle 3.2 */ public static function signup_user_parameters() { return new external_function_parameters( array( 'username' => new external_value(core_user::get_property_type('username'), 'Username'), 'password' => new external_value(core_user::get_property_type('password'), 'Plain text password'), 'firstname' => new external_value(core_user::get_property_type('firstname'), 'The first name(s) of the user'), 'lastname' => new external_value(core_user::get_property_type('lastname'), 'The family name of the user'), 'email' => new external_value(core_user::get_property_type('email'), 'A valid and unique email address'), 'city' => new external_value(core_user::get_property_type('city'), 'Home city of the user', VALUE_DEFAULT, ''), 'country' => new external_value(core_user::get_property_type('country'), 'Home country code', VALUE_DEFAULT, ''), 'recaptchachallengehash' => new external_value(PARAM_RAW, 'Recaptcha challenge hash', VALUE_DEFAULT, ''), 'recaptcharesponse' => new external_value(PARAM_NOTAGS, 'Recaptcha response', VALUE_DEFAULT, ''), 'customprofilefields' => new external_multiple_structure( new external_single_structure( array( 'type' => new external_value(PARAM_ALPHANUMEXT, 'The type of the custom field'), 'name' => new external_value(PARAM_ALPHANUMEXT, 'The name of the custom field'), 'value' => new external_value(PARAM_RAW, 'Custom field value, can be an encoded json if required') ) ), 'User custom fields (also known as user profile fields)', VALUE_DEFAULT, array() ), 'redirect' => new external_value(PARAM_LOCALURL, 'Redirect the user to this site url after confirmation.', VALUE_DEFAULT, ''), ) ); } /** * Get the signup required settings and profile fields. * * @param string $username username * @param string $password plain text password * @param string $firstname the first name(s) of the user * @param string $lastname the family name of the user * @param string $email a valid and unique email address * @param string $city home city of the user * @param string $country home country code * @param string $recaptchachallengehash recaptcha challenge hash * @param string $recaptcharesponse recaptcha response * @param array $customprofilefields user custom fields (also known as user profile fields) * @param string $redirect Site url to redirect the user after confirmation * @return array settings and possible warnings * @since Moodle 3.2 * @throws moodle_exception * @throws invalid_parameter_exception */ public static function signup_user($username, $password, $firstname, $lastname, $email, $city = '', $country = '', $recaptchachallengehash = '', $recaptcharesponse = '', $customprofilefields = array(), $redirect = '') { global $CFG, $PAGE; $warnings = array(); $params = self::validate_parameters( self::signup_user_parameters(), array( 'username' => $username, 'password' => $password, 'firstname' => $firstname, 'lastname' => $lastname, 'email' => $email, 'city' => $city, 'country' => $country, 'recaptchachallengehash' => $recaptchachallengehash, 'recaptcharesponse' => $recaptcharesponse, 'customprofilefields' => $customprofilefields, 'redirect' => $redirect, ) ); // We need this to make work the format text functions. $context = context_system::instance(); $PAGE->set_context($context); self::check_signup_enabled(); // Validate profile fields param types. $allowedfields = profile_get_signup_fields(); $fieldproperties = array(); $fieldsrequired = array(); foreach ($allowedfields as $field) { $fieldproperties[$field->object->inputname] = $field->object->get_field_properties(); if ($field->object->is_required()) { $fieldsrequired[$field->object->inputname] = true; } } foreach ($params['customprofilefields'] as $profilefield) { if (!array_key_exists($profilefield['name'], $fieldproperties)) { throw new invalid_parameter_exception('Invalid field' . $profilefield['name']); } list($type, $allownull) = $fieldproperties[$profilefield['name']]; validate_param($profilefield['value'], $type, $allownull); // Remove from the potential required list. if (isset($fieldsrequired[$profilefield['name']])) { unset($fieldsrequired[$profilefield['name']]); } } if (!empty($fieldsrequired)) { throw new invalid_parameter_exception('Missing required parameters: ' . implode(',', array_keys($fieldsrequired))); } // Validate the data sent. $data = $params; $data['email2'] = $data['email']; // Force policy agreed if a site policy is set. The client is responsible of implementing the interface check. $manager = new \core_privacy\local\sitepolicy\manager(); if ($manager->is_defined()) { $data['policyagreed'] = 1; } unset($data['recaptcharesponse']); unset($data['customprofilefields']); // Add profile fields data. foreach ($params['customprofilefields'] as $profilefield) { // First, check if the value is a json (some profile fields like text area uses an array for sending data). $datadecoded = json_decode($profilefield['value'], true); if (is_array($datadecoded) && (json_last_error() == JSON_ERROR_NONE)) { $data[$profilefield['name']] = $datadecoded; } else { $data[$profilefield['name']] = $profilefield['value']; } } $errors = signup_validate_data($data, array()); // Validate recaptcha. if (signup_captcha_enabled()) { require_once($CFG->libdir . '/recaptchalib_v2.php'); $response = recaptcha_check_response(RECAPTCHA_VERIFY_URL, $CFG->recaptchaprivatekey, getremoteaddr(), $params['recaptcharesponse']); if (!$response['isvalid']) { $errors['recaptcharesponse'] = $response['error']; } } if (!empty($errors)) { foreach ($errors as $itemname => $message) { $warnings[] = array( 'item' => $itemname, 'itemid' => 0, 'warningcode' => 'fielderror', 'message' => s($message) ); } $result = array( 'success' => false, 'warnings' => $warnings, ); } else { // Save the user. $user = signup_setup_new_user((object) $data); $authplugin = get_auth_plugin('email'); // Check if we should redirect the user once the user is confirmed. $confirmationurl = null; if (!empty($params['redirect'])) { // Pass via moodle_url to fix thinks like admin links. $redirect = new moodle_url($params['redirect']); $confirmationurl = new moodle_url('/login/confirm.php', array('redirect' => $redirect->out())); } $authplugin->user_signup_with_confirmation($user, false, $confirmationurl); $result = array( 'success' => true, 'warnings' => array(), ); } return $result; } /** * Describes the signup_user return value. * * @return external_single_structure * @since Moodle 3.2 */ public static function signup_user_returns() { return new external_single_structure( array( 'success' => new external_value(PARAM_BOOL, 'True if the user was created false otherwise'), 'warnings' => new external_warnings(), ) ); } } PK,B\Qrxclasses/privacy/provider.phpnu[. /** * Privacy class for requesting user data. * * @package message_email * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace message_email\privacy; defined('MOODLE_INTERNAL') || die(); use \core_privacy\local\metadata\collection; use \core_privacy\local\request\contextlist; use \core_privacy\local\request\approved_contextlist; use core_privacy\local\request\userlist; use core_privacy\local\request\writer; use \core_privacy\local\request\approved_userlist; /** * Privacy class for requesting user data. * * @package message_email * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\user_preference_provider, \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { $messageemailmessages = [ 'useridto' => 'privacy:metadata:message_email_messages:useridto', 'conversationid' => 'privacy:metadata:message_email_messages:conversationid', 'messageid' => 'privacy:metadata:message_email_messages:messageid', ]; // Note - this data gets deleted once the scheduled task runs. $collection->add_database_table('message_email_messages', $messageemailmessages, 'privacy:metadata:message_email_messages'); $collection->link_external_location('smtp', [ 'recipient' => 'privacy:metadata:recipient', 'userfrom' => 'privacy:metadata:userfrom', 'subject' => 'privacy:metadata:subject', 'fullmessage' => 'privacy:metadata:fullmessage', 'fullmessagehtml' => 'privacy:metadata:fullmessagehtml', 'attachment' => 'privacy:metadata:attachment', 'attachname' => 'privacy:metadata:attachname', 'replyto' => 'privacy:metadata:replyto', 'replytoname' => 'privacy:metadata:replytoname' ], 'privacy:metadata:externalpurpose'); return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { $contextlist = new contextlist(); return $contextlist; } /** * Get the list of users who have data within a context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { } /** * 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) { } /** * Delete all use data which matches the specified deletion_criteria. * * @param context $context A user context. */ public static function delete_data_for_all_users_in_context(\context $context) { } /** * 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) { } /** * 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) { } /** * Export all user preferences for the plugin * * @param int $userid */ public static function export_user_preferences(int $userid) { $preference = get_user_preferences('message_processor_email_email', null, $userid); if (!empty($preference)) { writer::export_user_preference( 'message_email', 'email', $preference, get_string('privacy:preference:email', 'message_email') ); } } } PK,B\aG&& upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This files describes API changes in /auth/email/*, information provided here is intended especially for developers. === 4.4 === * External function auth_email_external::get_signup_settings() now returns the field "extendedusernamechars". === 3.3 === * The config.html file was migrated to use the admin settings API. The identifier for configuration data stored in config_plugins table was converted from 'auth/email' to 'auth_email'. PK,B\,f7&7& tests/external/external_test.phpnu[. /** * Auth email external functions tests. * * @package auth_email * @category external * @copyright 2016 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.2 */ namespace auth_email\external; use auth_email_external; use externallib_advanced_testcase; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); /** * External auth email API tests. * * @package auth_email * @copyright 2016 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.2 */ final class external_test extends externallib_advanced_testcase { /** @var int custom profile field1 ID. */ protected $field1; /** @var int custom profile field2 ID. */ protected $field2; /** * Set up for every test */ public function setUp(): void { global $CFG; parent::setUp(); $this->resetAfterTest(true); $CFG->registerauth = 'email'; $this->field1 = $this->getDataGenerator()->create_custom_profile_field(array( 'shortname' => 'frogname', 'name' => 'Name of frog', 'datatype' => 'text', 'signup' => 1, 'visible' => 1, 'required' => 1, 'sortorder' => 1))->id; $this->field2 = $this->getDataGenerator()->create_custom_profile_field(array( 'shortname' => 'sometext', 'name' => 'Some text in textarea', 'datatype' => 'textarea', 'signup' => 1, 'visible' => 1, 'required' => 1, 'sortorder' => 2))->id; } public function test_get_signup_settings(): void { global $CFG; $CFG->defaultcity = 'Bcn'; $CFG->country = 'ES'; $CFG->sitepolicy = 'https://moodle.org'; $result = auth_email_external::get_signup_settings(); $result = \core_external\external_api::clean_returnvalue(auth_email_external::get_signup_settings_returns(), $result); // Check expected data. $this->assertEquals(array('firstname', 'lastname'), $result['namefields']); $this->assertEquals($CFG->defaultcity, $result['defaultcity']); $this->assertEquals($CFG->country, $result['country']); $this->assertEquals($CFG->sitepolicy, $result['sitepolicy']); $this->assertEquals(print_password_policy(), $result['passwordpolicy']); $this->assertNotContains('recaptchachallengehash', $result); $this->assertNotContains('recaptchachallengeimage', $result); // Check if the extended username chars is returning false when is not set. $this->assertFalse($result['extendedusernamechars']); // Whip up a array with named entries to easily check against. $namedarray = array(); foreach ($result['profilefields'] as $key => $value) { $namedarray[$value['shortname']] = array( 'datatype' => $value['datatype'] ); } // Just check if we have the fields from this test. If a plugin adds fields we'll let it slide. $this->assertArrayHasKey('frogname', $namedarray); $this->assertArrayHasKey('sometext', $namedarray); $this->assertEquals('text', $namedarray['frogname']['datatype']); $this->assertEquals('textarea', $namedarray['sometext']['datatype']); $CFG->extendedusernamechars = true; $result = auth_email_external::get_signup_settings(); $result = \core_external\external_api::clean_returnvalue(auth_email_external::get_signup_settings_returns(), $result); $this->assertTrue($result['extendedusernamechars']); } /** * Test get_signup_settings with mathjax in a profile field. */ public function test_get_signup_settings_with_mathjax_in_profile_fields(): void { // Enable MathJax filter in content and headings. $this->configure_filters([ ['name' => 'mathjaxloader', 'state' => TEXTFILTER_ON, 'move' => -1, 'applytostrings' => true], ]); // Create category with MathJax and a new field with MathJax. $categoryname = 'Cat $$(a+b)=2$$'; $fieldname = 'Some text $$(a+b)=2$$'; $categoryid = $this->getDataGenerator()->create_custom_profile_field_category(['name' => $categoryname])->id; $this->getDataGenerator()->create_custom_profile_field(array( 'shortname' => 'mathjaxname', 'name' => $fieldname, 'categoryid' => $categoryid, 'datatype' => 'textarea', 'signup' => 1, 'visible' => 1, 'required' => 1, 'sortorder' => 2)); $result = auth_email_external::get_signup_settings(); $result = \core_external\external_api::clean_returnvalue(auth_email_external::get_signup_settings_returns(), $result); // Format the original data. $sitecontext = \context_system::instance(); $categoryname = \core_external\util::format_string($categoryname, $sitecontext->id); $fieldname = \core_external\util::format_string($fieldname, $sitecontext->id); // Whip up a array with named entries to easily check against. $namedarray = array(); foreach ($result['profilefields'] as $key => $value) { $namedarray[$value['shortname']] = $value; } // Check the new profile field. $this->assertArrayHasKey('mathjaxname', $namedarray); $this->assertStringContainsString('', $namedarray['mathjaxname']['categoryname']); $this->assertStringContainsString('', $namedarray['mathjaxname']['name']); $this->assertEquals($categoryname, $namedarray['mathjaxname']['categoryname']); $this->assertEquals($fieldname, $namedarray['mathjaxname']['name']); } public function test_signup_user(): void { global $DB; $username = 'pepe'; $password = 'abcdefAª.ªª!!3'; $firstname = 'Pepe'; $lastname = 'Pérez'; $email = 'myemail@no.zbc'; $city = 'Bcn'; $country = 'ES'; $customprofilefields = array( array( 'type' => 'text', 'name' => 'profile_field_frogname', 'value' => 'random text', ), array( 'type' => 'textarea', 'name' => 'profile_field_sometext', 'value' => json_encode( array( 'text' => 'blah blah', 'format' => FORMAT_HTML ) ), ) ); // Create new user. $result = auth_email_external::signup_user($username, $password, $firstname, $lastname, $email, $city, $country, '', '', $customprofilefields); $result = \core_external\external_api::clean_returnvalue(auth_email_external::signup_user_returns(), $result); $this->assertTrue($result['success']); $this->assertEmpty($result['warnings']); $user = $DB->get_record('user', array('username' => $username)); $this->assertEquals($firstname, $user->firstname); $this->assertEquals($lastname, $user->lastname); $this->assertEquals($email, $user->email); $this->assertEquals($city, $user->city); $this->assertEquals($country, $user->country); $this->assertEquals(0, $user->confirmed); $this->assertEquals(current_language(), $user->lang); $this->assertEquals('email', $user->auth); $infofield = $DB->get_record('user_info_data', array('userid' => $user->id, 'fieldid' => $this->field1)); $this->assertEquals($customprofilefields[0]['value'], $infofield->data); $infofield = $DB->get_record('user_info_data', array('userid' => $user->id, 'fieldid' => $this->field2)); $this->assertEquals(json_decode($customprofilefields[1]['value'])->text, $infofield->data); // Try to create a user with the same username, email and password. We ommit also the profile fields. $password = 'abc'; $result = auth_email_external::signup_user($username, $password, $firstname, $lastname, $email, $city, $country, '', '', $customprofilefields); $result = \core_external\external_api::clean_returnvalue(auth_email_external::signup_user_returns(), $result); $this->assertFalse($result['success']); $this->assertCount(3, $result['warnings']); $expectederrors = array('username', 'email', 'password'); $finalerrors = []; foreach ($result['warnings'] as $warning) { $finalerrors[] = $warning['item']; } $this->assertEquals($expectederrors, $finalerrors); // Do not pass the required profile fields. $this->expectException('invalid_parameter_exception'); $result = auth_email_external::signup_user($username, $password, $firstname, $lastname, $email, $city, $country); } } PK,B\g$$ tests/behat/behat_auth_email.phpnu[. /** * Step definition for auth_email * * @package auth_email * @category test * @copyright 2018 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); /** * Step definition for auth_email. * * @package auth_email * @category test * @copyright 2018 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_auth_email extends behat_base { /** * Emulate clicking on confirmation link from the email * * @When /^I confirm email for "(?P(?:[^"]|\\")*)"$/ * * @param string $username */ public function i_confirm_email_for($username) { global $DB; $secret = $DB->get_field('user', 'secret', ['username' => $username], MUST_EXIST); $confirmationurl = new moodle_url('/login/confirm.php'); $confirmationpath = $confirmationurl->out_as_local_url(false); $url = $confirmationpath . '?' . 'data='. $secret .'/'. $username; $this->execute('behat_general::i_visit', [$url]); } } PK,B\*ex/22tests/behat/signup.featurenu[@auth @auth_email Feature: User must accept policy when logging in and signing up In order to record user agreement to use the site As a user I need to be able to accept site policy during sign up Scenario: Accept policy on sign up, no site policy Given the following config values are set as admin: | registerauth | email | | passwordpolicy | 0 | And I am on site homepage And I follow "Log in" When I click on "Create new account" "link" Then I should not see "I understand and agree" And I set the following fields to these values: | Username | user1 | | Password | user1 | | Email address | student1@example.com | | Email (again) | student1@example.com | | First name | User1 | | Last name | L1 | And I press "Create my new account" And I should see "Confirm your account" And I should see "An email should have been sent to your address at student1@example.com" And I confirm email for "user1" And I should see "Thanks, User1 L1" And I should see "Your registration has been confirmed" And I open my profile in edit mode And the field "First name" matches value "User1" And I log out # Confirm that user can login and browse the site (edit their profile). And I log in as "user1" And I open my profile in edit mode And the field "First name" matches value "User1" Scenario: Accept policy on sign up, with site policy Given the following config values are set as admin: | registerauth | email | | passwordpolicy | 0 | | sitepolicy | https://moodle.org | And I am on site homepage And I follow "Log in" When I click on "Create new account" "link" Then the field "I understand and agree" matches value "0" And I set the following fields to these values: | Username | user1 | | Password | user1 | | Email address | user1@example.com | | Email (again) | user1@example.com | | First name | User1 | | Last name | L1 | | I understand and agree | 1 | And I press "Create my new account" And I should see "Confirm your account" And I should see "An email should have been sent to your address at user1@example.com" And I confirm email for "user1" And I should see "Thanks, User1 L1" And I should see "Your registration has been confirmed" And I open my profile in edit mode And the field "First name" matches value "User1" And I log out # Confirm that user is not asked to agree to site policy again after the next login. And I log in as "user1" And I open my profile in edit mode And the field "First name" matches value "User1" Scenario Outline: Email validation during email registration Given the following config values are set as admin: | allowaccountssameemail | | | registerauth | email | | passwordpolicy | 0 | And the following "users" exist: | username | firstname | lastname | email | | s1 | John | Doe | s1@example.com | And I am on site homepage And I follow "Log in" When I click on "Create new account" "link" And I set the following fields to these values: | Username | s2 | | Password | test | | Email address | | | Email (again) | | | First name | Jane | | Last name | Doe | And I press "Create my new account" Then I should "This email address is already registered. Perhaps you created an account in the past?" And I should "Invalid email address" Examples: | allowsameemail | email1 | email2 | expect | expect2 | | 0 | s1@example.com | s1@example.com | see | not see | | 0 | S1@EXAMPLE.COM | S1@EXAMPLE.COM | see | not see | | 0 | s1@example.com | S1@EXAMPLE.COM | see | not see | | 0 | s2@example.com | s1@example.com | not see | see | | 1 | s1@example.com | s1@example.com | not see | not see | | 1 | S1@EXAMPLE.COM | S1@EXAMPLE.COM | not see | not see | | 1 | s1@example.com | S1@EXAMPLE.COM | not see | not see | | 1 | s1@example.com | s2@example.com | not see | see | PK,B\\$auth.phpnu[. /** * Authentication Plugin: Email Authentication * * @author Martin Dougiamas * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package auth_email */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir.'/authlib.php'); /** * Email authentication plugin. */ class auth_plugin_email extends auth_plugin_base { /** * Constructor. */ public function __construct() { $this->authtype = 'email'; $this->config = get_config('auth_email'); } /** * Old syntax of class constructor. Deprecated in PHP7. * * @deprecated since Moodle 3.1 */ public function auth_plugin_email() { debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); self::__construct(); } /** * Returns true if the username and password work and false if they are * wrong or don't exist. * * @param string $username The username * @param string $password The password * @return bool Authentication success or failure. */ function user_login($username, $password) { global $CFG, $DB; if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id))) { return validate_internal_user_password($user, $password); } return false; } /** * Updates the user's password. * * called when the user password is updated. * * @param object $user User table object (with system magic quotes) * @param string $newpassword Plaintext password (with system magic quotes) * @return boolean result * */ function user_update_password($user, $newpassword) { $user = get_complete_user_data('id', $user->id); // This will also update the stored hash to the latest algorithm // if the existing hash is using an out-of-date algorithm (or the // legacy md5 algorithm). return update_internal_user_password($user, $newpassword); } function can_signup() { return true; } /** * Sign up a new user ready for confirmation. * Password is passed in plaintext. * * @param object $user new user object * @param boolean $notify print notice with link and terminate */ function user_signup($user, $notify=true) { // Standard signup, without custom confirmatinurl. return $this->user_signup_with_confirmation($user, $notify); } /** * Sign up a new user ready for confirmation. * * Password is passed in plaintext. * A custom confirmationurl could be used. * * @param object $user new user object * @param boolean $notify print notice with link and terminate * @param string $confirmationurl user confirmation URL * @return boolean true if everything well ok and $notify is set to true * @throws moodle_exception * @since Moodle 3.2 */ public function user_signup_with_confirmation($user, $notify=true, $confirmationurl = null) { global $CFG, $DB, $SESSION; require_once($CFG->dirroot.'/user/profile/lib.php'); require_once($CFG->dirroot.'/user/lib.php'); $plainpassword = $user->password; $user->password = hash_internal_user_password($user->password); if (empty($user->calendartype)) { $user->calendartype = $CFG->calendartype; } $user->id = user_create_user($user, false, false); user_add_password_history($user->id, $plainpassword); // Save any custom profile field information. profile_save_data($user); // Save wantsurl against user's profile, so we can return them there upon confirmation. if (!empty($SESSION->wantsurl)) { set_user_preference('auth_email_wantsurl', $SESSION->wantsurl, $user); } // Trigger event. \core\event\user_created::create_from_userid($user->id)->trigger(); if (! send_confirmation_email($user, $confirmationurl)) { throw new \moodle_exception('auth_emailnoemail', 'auth_email'); } if ($notify) { global $CFG, $PAGE, $OUTPUT; $emailconfirm = get_string('emailconfirm'); $PAGE->navbar->add($emailconfirm); $PAGE->set_title($emailconfirm); $PAGE->set_heading($PAGE->course->fullname); echo $OUTPUT->header(); notice(get_string('emailconfirmsent', '', $user->email), "$CFG->wwwroot/index.php"); } else { return true; } } /** * Returns true if plugin allows confirming of new users. * * @return bool */ function can_confirm() { return true; } /** * Confirm the new user as registered. * * @param string $username * @param string $confirmsecret */ function user_confirm($username, $confirmsecret) { global $DB, $SESSION; $user = get_complete_user_data('username', $username); if (!empty($user)) { if ($user->auth != $this->authtype) { return AUTH_CONFIRM_ERROR; } else if ($user->secret === $confirmsecret && $user->confirmed) { return AUTH_CONFIRM_ALREADY; } else if ($user->secret === $confirmsecret) { // They have provided the secret key to get in $DB->set_field("user", "confirmed", 1, array("id"=>$user->id)); if ($wantsurl = get_user_preferences('auth_email_wantsurl', false, $user)) { // Ensure user gets returned to page they were trying to access before signing up. $SESSION->wantsurl = $wantsurl; unset_user_preference('auth_email_wantsurl', $user); } return AUTH_CONFIRM_OK; } } else { return AUTH_CONFIRM_ERROR; } } function prevent_local_passwords() { return false; } /** * Returns true if this authentication plugin is 'internal'. * * @return bool */ function is_internal() { return true; } /** * Returns true if this authentication plugin can change the user's * password. * * @return bool */ function can_change_password() { return true; } /** * Returns the URL for changing the user's pw, or empty if the default can * be used. * * @return moodle_url */ function change_password_url() { return null; // use default internal method } /** * Returns true if plugin allows resetting of internal password. * * @return bool */ function can_reset_password() { return true; } /** * Returns true if plugin can be manually set. * * @return bool */ function can_be_manually_set() { return true; } /** * Returns whether or not the captcha element is enabled. * @return bool */ function is_captcha_enabled() { return get_config("auth_{$this->authtype}", 'recaptcha'); } } PK,B\4J settings.phpnu[. /** * Admin settings and defaults. * * @package auth_email * @copyright 2017 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; if ($ADMIN->fulltree) { // Introductory explanation. $settings->add(new admin_setting_heading('auth_email/pluginname', '', new lang_string('auth_emaildescription', 'auth_email'))); $options = array( new lang_string('no'), new lang_string('yes'), ); $settings->add(new admin_setting_configselect('auth_email/recaptcha', new lang_string('auth_emailrecaptcha_key', 'auth_email'), new lang_string('auth_emailrecaptcha', 'auth_email'), 0, $options)); // Display locking / mapping of profile fields. $authplugin = get_auth_plugin('email'); display_auth_lock_options($settings, $authplugin->authtype, $authplugin->userfields, get_string('auth_fieldlocks_help', 'auth'), false, false); } PKS\Fdb/install.phpnu[. /** * Installation code for the email message processor * * @package message_email * @copyright 2009 Moodle Pty Ltd (http://moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Install the email message processor */ function xmldb_message_email_install() { global $DB; $result = true; $provider = new stdClass(); $provider->name = 'email'; $DB->insert_record('message_processors', $provider); return $result; } PKS\[È db/tasks.phpnu[. /** * This file defines tasks performed by the plugin. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); // List of tasks. $tasks = array( array( 'classname' => 'message_email\task\send_email_task', 'blocking' => 0, 'minute' => 0, 'hour' => 22, 'day' => '*', 'dayofweek' => '*', 'month' => '*' ) ); PKS\~db/install.xmlnu[
PKS\tf db/events.phpnu[. /** * This file defines what events we wish to observe and the method responsible for handling the event. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $observers = [ [ 'eventname' => '\core\event\message_viewed', 'callback' => '\message_email\event_observers::message_viewed', ], ]; PKS\SR lang/en/message_email.phpnu[. /** * Strings for component 'message_email', language 'en' * * @package message_email * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['email'] = 'Send email notifications to'; $string['emaildigestunreadmessages'] = 'Unread messages'; $string['emaildigestviewallmessages'] = 'View all messages'; $string['emailonlyfromnoreplyaddress'] = 'Always send email from the no-reply address?'; $string['ifemailleftempty'] = 'Leave empty to send notifications to {$a}'; $string['messagedigestemailsubject'] = '{$a}: Messages digest'; $string['pluginname'] = 'Email'; $string['privacy:metadata:attachment'] = 'A file on the filesystem.'; $string['privacy:metadata:attachname'] = 'The name of the attached file (extension indicates MIME).'; $string['privacy:metadata:externalpurpose'] = 'This information is sent to an external SMTP server to be ultimately delivered as an email to the recipient.'; $string['privacy:metadata:fullmessage'] = 'The full message in a given format.'; $string['privacy:metadata:fullmessagehtml'] = 'The full version of the message.'; $string['privacy:metadata:message_email_messages'] = 'The list of users enrolled via an LTI provider'; $string['privacy:metadata:message_email_messages:conversationid'] = 'The ID of the conversation being sent to'; $string['privacy:metadata:message_email_messages:messageid'] = 'The ID of the message being sent'; $string['privacy:metadata:message_email_messages:useridto'] = 'The ID of the user receiving the message'; $string['privacy:metadata:recipient'] = 'The recipient of the message.'; $string['privacy:metadata:replyto'] = 'The email address to reply to.'; $string['privacy:metadata:replytoname'] = 'Name of reply to recipient.'; $string['privacy:metadata:subject'] = 'The subject line of the message.'; $string['privacy:metadata:userfrom'] = 'The user sending the message.'; $string['privacy:preference:email'] = 'Preferred email notification address'; $string['tasksendemail'] = 'Messages digest mailings'; PKS\mӁclasses/event_observers.phpnu[. /** * Contains an observer class containing methods for handling events. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace message_email; defined('MOODLE_INTERNAL') || die(); /** * Observer class containing methods for handling events. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class event_observers { /** * Message viewed event handler. * * @param \core\event\message_viewed $event The message viewed event. */ public static function message_viewed(\core\event\message_viewed $event) { global $DB; $userid = $event->userid; $messageid = $event->other['messageid']; $DB->delete_records('message_email_messages', ['useridto' => $userid, 'messageid' => $messageid]); } } PKS\$Abbclasses/output/renderer.phpnu[. /** * Contains renderer class. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace message_email\output; defined('MOODLE_INTERNAL') || die(); use plugin_renderer_base; /** * Renderer class. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer extends plugin_renderer_base { /** * Formats the email used to send the certificate by the email_certificate_task. * * @param email_digest $emaildigest The certificate to email * @return string */ public function render_email_digest(email_digest $emaildigest) { $data = $emaildigest->export_for_template($this); return $this->render_from_template('message_email/' . $this->get_template_name(), $data); } } PKS\ J ??classes/output/email_digest.phpnu[. /** * Email digest renderable. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace message_email\output; defined('MOODLE_INTERNAL') || die(); /** * Email digest renderable. * * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class email_digest implements \renderable, \templatable { /** * @var array The conversations */ protected $conversations = array(); /** * @var array The messages */ protected $messages = array(); /** * @var \stdClass The user we want to send the digest email to */ protected $userto; /** * The email_digest constructor. * * @param \stdClass $userto */ public function __construct(\stdClass $userto) { $this->userto = $userto; } /** * Adds another conversation to this digest. * * @param \stdClass $conversation The conversation from the 'message_conversations' table. */ public function add_conversation(\stdClass $conversation) { $this->conversations[$conversation->id] = $conversation; } /** * Adds another message to this digest, using the conversation id it belongs to as a key. * * @param \stdClass $message The message from the 'messages' table. */ public function add_message(\stdClass $message) { $this->messages[$message->conversationid][] = $message; } /** * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $renderer The render to be used for formatting the email * @return \stdClass The data ready for use in a mustache template */ public function export_for_template(\renderer_base $renderer) { global $PAGE; // Prepare the data we are going to send to the template. $data = new \stdClass(); $data->conversations = []; // Don't do anything if there are no messages. foreach ($this->conversations as $conversation) { $messages = $this->messages[$conversation->id] ?? []; if (empty($messages)) { continue; } $viewallmessageslink = new \moodle_url('/message/index.php', ['convid' => $conversation->id]); $group = new \stdClass(); $group->id = $conversation->groupid; $group->picture = $conversation->picture; $group->courseid = $conversation->courseid; $grouppictureurl = $renderer->image_url('g/g1')->out(false); // Default image. if ($url = get_group_picture_url($group, $group->courseid, false, true)) { $grouppictureurl = $url->out(false); } $coursecontext = \context_course::instance($conversation->courseid); $conversationformatted = new \stdClass(); $conversationformatted->groupname = format_string($conversation->name, true, ['context' => $coursecontext]); $conversationformatted->grouppictureurl = $grouppictureurl; $conversationformatted->coursename = format_string($conversation->coursename, true, ['context' => $coursecontext]); $conversationformatted->numberofunreadmessages = count($messages); $conversationformatted->messages = []; $conversationformatted->viewallmessageslink = \html_writer::link($viewallmessageslink, get_string('emaildigestviewallmessages', 'message_email')); // We only display the last 3 messages. $messages = array_slice($messages, -3, 3, true); foreach ($messages as $message) { $user = new \stdClass(); username_load_fields_from_object($user, $message); $user->picture = $message->picture; $user->imagealt = $message->imagealt; $user->email = $message->email; $user->id = $message->useridfrom; $userpicture = new \user_picture($user); $userpicture->includetoken = true; $userpictureurl = $userpicture->get_url($PAGE)->out(false); $messageformatted = new \stdClass(); $messageformatted->userpictureurl = $userpictureurl; $messageformatted->userfullname = fullname($user); $messageformatted->message = message_format_message_text($message); // Check if the message was sent today. $istoday = userdate($message->timecreated, 'Y-m-d') == userdate(time(), 'Y-m-d'); if ($istoday) { $timesent = userdate($message->timecreated, get_string('strftimetime24', 'langconfig')); } else { $timesent = userdate($message->timecreated, get_string('strftimedatefullshort', 'langconfig')); } $messageformatted->timesent = $timesent; $conversationformatted->messages[] = $messageformatted; } $data->conversations[] = $conversationformatted; } return $data; } } PKS\C]rr!classes/output/email/renderer.phpnu[. /** * Email digest as html renderer. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace message_email\output\email; defined('MOODLE_INTERNAL') || die(); /** * Email digest as html renderer. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer extends \message_email\output\renderer { /** * The template name for this renderer. * * @return string */ public function get_template_name() { return 'email_digest_html'; } } PKS\e||+classes/output/email/renderer_textemail.phpnu[. /** * Email digest as text renderer. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace message_email\output\email; defined('MOODLE_INTERNAL') || die(); /** * Email digest as text renderer. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer_textemail extends \message_email\output\renderer { /** * The template name for this renderer. * * @return string */ public function get_template_name() { return 'email_digest_text'; } } PKS\{l<88 classes/task/send_email_task.phpnu[. /** * Contains the class responsible for sending emails as a digest. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace message_email\task; use core\task\scheduled_task; use moodle_recordset; defined('MOODLE_INTERNAL') || die(); /** * Class responsible for sending emails as a digest. * * @package message_email * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class send_email_task extends scheduled_task { /** * @var int $maxid This is the maximum id of the message in 'message_email_messages'. * We use this so we know what records to process, as more records may be added * while this task runs. */ private $maxid; /** * Get a descriptive name for this task (shown to admins). * * @return string */ public function get_name() { return get_string('tasksendemail', 'message_email'); } /** * Send out emails. */ public function execute() { global $DB, $PAGE, $SITE; // Get the maximum id we are going to use. // We use this as records may be added to the table while this task runs. $this->maxid = $DB->get_field_sql("SELECT MAX(id) FROM {message_email_messages}"); // We are going to send these emails from 'noreplyaddress'. $noreplyuser = \core_user::get_noreply_user(); // The renderers used for sending emails. $htmlrenderer = $PAGE->get_renderer('message_email', 'email', 'htmlemail'); $textrenderer = $PAGE->get_renderer('message_email', 'email', 'textemail'); // Keep track of which emails failed to send. $users = $this->get_unique_users(); foreach ($users as $user) { \core\cron::setup_user($user); $hascontent = false; $renderable = new \message_email\output\email_digest($user); $conversations = $this->get_conversations_for_user($user->id); foreach ($conversations as $conversation) { $renderable->add_conversation($conversation); $messages = $this->get_users_messages_for_conversation($conversation->id, $user->id); if ($messages->valid()) { $hascontent = true; foreach ($messages as $message) { $renderable->add_message($message); } } $messages->close(); } $conversations->close(); if ($hascontent) { $subject = get_string('messagedigestemailsubject', 'message_email', format_string($SITE->fullname)); $message = $textrenderer->render($renderable); $messagehtml = $htmlrenderer->render($renderable); if (email_to_user($user, $noreplyuser, $subject, $message, $messagehtml)) { $DB->delete_records_select('message_email_messages', 'useridto = ? AND id <= ?', [$user->id, $this->maxid]); } } } \core\cron::setup_user(); $users->close(); } /** * Returns an array of users in the given conversation. * * @return moodle_recordset A moodle_recordset instance. */ private function get_unique_users(): moodle_recordset { global $DB; $subsql = 'SELECT DISTINCT(useridto) as id FROM {message_email_messages} WHERE id <= ?'; $sql = "SELECT * FROM {user} u WHERE id IN ($subsql)"; return $DB->get_recordset_sql($sql, [$this->maxid]); } /** * Returns an array of unique conversations that require processing. * * @param int $userid The ID of the user we are sending a digest to. * @return moodle_recordset A moodle_recordset instance. */ private function get_conversations_for_user(int $userid): moodle_recordset { global $DB; // We shouldn't be joining directly on the group table as group // conversations may (in the future) be something created that // isn't related to an actual group in a course. However, for // now this will have to do before 3.7 code freeze. // See related MDL-63814. $sql = "SELECT DISTINCT mc.id, mc.name, c.id as courseid, c.fullname as coursename, g.id as groupid, g.picture FROM {message_conversations} mc JOIN {groups} g ON mc.itemid = g.id JOIN {course} c ON g.courseid = c.id JOIN {message_email_messages} mem ON mem.conversationid = mc.id WHERE mem.useridto = ? AND mem.id <= ?"; return $DB->get_recordset_sql($sql, [$userid, $this->maxid]); } /** * Returns the messages to send to a user for a given conversation * * @param int $conversationid * @param int $userid * @return moodle_recordset A moodle_recordset instance. */ protected function get_users_messages_for_conversation(int $conversationid, int $userid): moodle_recordset { global $DB; $userfieldsapi = \core_user\fields::for_userpic(); $usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT $usernamefields, m.* FROM {messages} m JOIN {user} u ON u.id = m.useridfrom JOIN {message_email_messages} mem ON mem.messageid = m.id WHERE mem.useridto = ? AND mem.conversationid = ? AND mem.id <= ?"; return $DB->get_recordset_sql($sql, [$userid, $conversationid, $this->maxid]); } } PKS\*3 $templates/email_digest_text.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 message_email/email_digest_text Template which defines a forum post for sending in a single-post HTML email. Classes required for JS: * none Data attributes required for JS: * none Example context (json): { "conversations": [ { "groupname": "Blue Students", "grouppictureurl": "http://example.com/image.jpg", "coursename": "Math 101", "numberofunreadmessages": "2", "messages": [ { "userfullname": "Chris Cross", "userpictureurl": "http://example.com/image.jpg", "message": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla neque nunc, bibendum ac vestibulum sit amet, scelerisque luctus sem. Maecenas ultricies hendrerit augue, ac venenatis odio volutpat nec", "timesent": "10:12" }, { "userfullname": "Irene Ipsum", "userpictureurl": "http://example.com/image.jpg", "message": "Etiam a tristique risus. Pellentesque id tellus eget elit dictum varius id sed sapien", "timesent": "10:14" } ], "viewallmessageslink": "http://example.com" } ] } }} {{#conversations}} {{{grouppictureurl}}} {{groupname}} {{coursename}} {{numberofunreadmessages}} {{#str}}emaildigestunreadmessages, message_email{{/str}} {{#messages}} {{{ userpictureurl }}} {{userfullname}} {{message}} {{timesent}} {{viewallmessageslink}} {{/messages}} {{/conversations}} PKS\$templates/email_digest_html.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 message_email/email_digest_html Template which defines a forum post for sending in a single-post HTML email. Classes required for JS: * none Data attributes required for JS: * none Example context (json): { "conversations": [ { "groupname": "Blue Students", "grouppictureurl": "http://example.com/image.jpg", "coursename": "Math 101", "numberofunreadmessages": "2", "messages": [ { "userfullname": "Chris Cross", "userpictureurl": "http://example.com/image.jpg", "message": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla neque nunc, bibendum ac vestibulum sit amet, scelerisque luctus sem. Maecenas ultricies hendrerit augue, ac venenatis odio volutpat nec", "timesent": "10:12" }, { "userfullname": "Irene Ipsum", "userpictureurl": "http://example.com/image.jpg", "message": "Etiam a tristique risus. Pellentesque id tellus eget elit dictum varius id sed sapien", "timesent": "10:14" } ], "viewallmessageslink": "http://example.com" } ] } }} {{#conversations}} {{#messages}} {{/messages}}
{{ groupname }}
{{ coursename }}
{{ numberofunreadmessages }} {{#str}} emaildigestunreadmessages, message_email {{/str}}
{{{ userfullname }}} {{ timesent }} {{{ message }}}
{{{viewallmessageslink}}}
{{/conversations}} PKS\ADn tests/privacy/provider_test.phpnu[. namespace message_email\privacy; use context_system; use core_message_external; use core_privacy\local\request\writer; use core_privacy\tests\provider_testcase; /** * Unit tests for message\output\email\classes\privacy\provider.php * * @package message_email * @covers \message_email\privacy\provider * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends provider_testcase { /** * Basic setup for these tests. */ public function setUp(): void { parent::setUp(); $this->resetAfterTest(true); } /** * Test returning metadata. */ public function test_get_metadata(): void { $collection = new \core_privacy\local\metadata\collection('message_email'); $collection = \message_email\privacy\provider::get_metadata($collection); $this->assertNotEmpty($collection); } /** * Test getting the context for the user ID related to this plugin. */ public function test_get_contexts_for_userid(): void { $user = $this->getDataGenerator()->create_user(); $contextlist = \message_email\privacy\provider::get_contexts_for_userid($user->id); $this->assertEmpty($contextlist); } /** * Test exporting user preferences */ public function test_export_user_preferences(): void { global $CFG; require_once("{$CFG->dirroot}/message/externallib.php"); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); // Submit configuration form, which adds the preferences.. core_message_external::message_processor_config_form($user->id, 'email', [ [ 'name' => 'email_email', 'value' => 'alternate@example.com', ], ]); // Switch to admin user (so we can validate preferences of the correct user are being exported). $this->setAdminUser(); provider::export_user_preferences($user->id); $writer = writer::with_context(context_system::instance()); $this->assertTrue($writer->has_any_data()); $preferences = $writer->get_user_preferences('message_email'); $this->assertNotEmpty($preferences->email); $this->assertEquals('alternate@example.com', $preferences->email->value); $this->assertEquals(get_string('privacy:preference:email', 'message_email'), $preferences->email->description); } } PKS\Rtests/task/send_email_test.phpnu[. namespace message_email\task; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/message/tests/messagelib_test.php'); /** * Class for testing the send email task. * * @package message_email * @category test * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class send_email_test extends \advanced_testcase { /** * Test sending email task. */ public function test_sending_email_task(): void { global $DB, $SITE; $this->preventResetByRollback(); // Messaging is not compatible with transactions. $this->resetAfterTest(); // Create a course. $course = $this->getDataGenerator()->create_course(); $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); // Create two groups in the course. $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); groups_add_member($group1->id, $user1->id); groups_add_member($group2->id, $user1->id); groups_add_member($group1->id, $user2->id); groups_add_member($group2->id, $user2->id); $conversation1 = \core_message\api::create_conversation( \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id], 'Group 1', \core_message\api::MESSAGE_CONVERSATION_ENABLED, 'core_group', 'groups', $group1->id, \context_course::instance($course->id)->id ); $conversation2 = \core_message\api::create_conversation( \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id], 'Group 2', \core_message\api::MESSAGE_CONVERSATION_ENABLED, 'core_group', 'groups', $group2->id, \context_course::instance($course->id)->id ); // Go through each conversation. if ($conversations = $DB->get_records('message_conversations')) { foreach ($conversations as $conversation) { $conversationid = $conversation->id; // Let's send 5 messages. for ($i = 1; $i <= 5; $i++) { $message = new \core\message\message(); $message->courseid = 1; $message->component = 'moodle'; $message->name = 'instantmessage'; $message->userfrom = $user1; $message->convid = $conversationid; $message->subject = 'message subject'; $message->fullmessage = 'message body'; $message->fullmessageformat = FORMAT_MARKDOWN; $message->fullmessagehtml = '

message body

'; $message->smallmessage = 'small message'; $message->notification = '0'; message_send($message); } } } $this->assertEquals(10, $DB->count_records('message_email_messages')); // Only 1 email is sent as the messages are included in it at a digest. $sink = $this->redirectEmails(); $task = new send_email_task(); $task->execute(); $this->assertEquals(1, $sink->count()); // Confirm it contains the correct data. $emails = $sink->get_messages(); $email = reset($emails); $sitename = format_string($SITE->fullname); $this->assertSame(get_string('messagedigestemailsubject', 'message_email', $sitename), $email->subject); $this->assertSame($user2->email, $email->to); $this->assertNotEmpty($email->header); $emailbody = quoted_printable_decode($email->body); $this->assertStringContainsString('Group 1', $emailbody); $this->assertStringContainsString('Group 2', $emailbody); // 5 unread messages per conversation, this will be listed twice. $this->assertMatchesRegularExpression("/]*>5<\/span> ]*>Unread message\w+/", $emailbody); // Confirm table was emptied after task was run. $this->assertEquals(0, $DB->count_records('message_email_messages')); // Confirm running it again does not send another. $sink = $this->redirectEmails(); $task = new send_email_task(); $task->execute(); $this->assertEquals(0, $sink->count()); } } PKS\o3e$$tests/event_observers_test.phpnu[. namespace message_email; /** * Class for testing the event observers. * * @package message_email * @category test * @copyright 2019 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class event_observers_test extends \advanced_testcase { /** * Test the message viewed event observer. */ public function test_message_viewed_observer(): void { global $DB; $this->preventResetByRollback(); // Messaging is not compatible with transactions. $this->resetAfterTest(); // Create the test data. $course = $this->getDataGenerator()->create_course(); $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); groups_add_member($group1->id, $user1->id); groups_add_member($group1->id, $user2->id); $conversation = \core_message\api::create_conversation( \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id], 'Group 1', \core_message\api::MESSAGE_CONVERSATION_ENABLED, 'core_group', 'groups', $group1->id, \context_course::instance($course->id)->id ); $message = new \core\message\message(); $message->courseid = 1; $message->component = 'moodle'; $message->name = 'instantmessage'; $message->userfrom = $user1; $message->convid = $conversation->id; $message->subject = 'message subject'; $message->fullmessage = 'message body'; $message->fullmessageformat = FORMAT_MARKDOWN; $message->fullmessagehtml = '

message body

'; $message->smallmessage = 'small message'; $message->notification = '0'; // Send the message twice. $messageid1 = message_send($message); $messageid2 = message_send($message); // Check there are now 2 messages pending to be sent in the digest. $this->assertEquals(2, $DB->count_records('message_email_messages')); // Mark one of the messages as read. $message1 = $DB->get_record('messages', ['id' => $messageid1]); \core_message\api::mark_message_as_read($user2->id, $message1); $emailmessage = $DB->get_records('message_email_messages'); // Check there is now only 1 message pending to be sent in the digest and it is the correct message. $this->assertEquals(1, count($emailmessage)); $emailmessage = reset($emailmessage); $this->assertEquals($user2->id, $emailmessage->useridto); $this->assertEquals($conversation->id, $emailmessage->conversationid); $this->assertEquals($messageid2, $emailmessage->messageid); } } PKS\4%%message_output_email.phpnu[. /** * Contains the definiton of the email message processors (sends messages to users via email) * * @package message_email * @copyright 2008 Luis Rodrigues and Martin Dougiamas * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once($CFG->dirroot.'/message/output/lib.php'); /** * The email message processor * * @package message_email * @copyright 2008 Luis Rodrigues and Martin Dougiamas * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class message_output_email extends message_output { /** * Processes the message (sends by email). * @param object $eventdata the event data submitted by the message sender plus $eventdata->savedmessageid */ function send_message($eventdata) { global $CFG, $DB; // skip any messaging suspended and deleted users if ($eventdata->userto->auth === 'nologin' or $eventdata->userto->suspended or $eventdata->userto->deleted) { return true; } //the user the email is going to $recipient = null; //check if the recipient has a different email address specified in their messaging preferences Vs their user profile $emailmessagingpreference = get_user_preferences('message_processor_email_email', null, $eventdata->userto); $emailmessagingpreference = clean_param($emailmessagingpreference, PARAM_EMAIL); // If the recipient has set an email address in their preferences use that instead of the one in their profile // but only if overriding the notification email address is allowed if (!empty($emailmessagingpreference) && !empty($CFG->messagingallowemailoverride)) { //clone to avoid altering the actual user object $recipient = clone($eventdata->userto); $recipient->email = $emailmessagingpreference; } else { $recipient = $eventdata->userto; } // Check if we have attachments to send. $attachment = ''; $attachname = ''; if (!empty($CFG->allowattachments) && !empty($eventdata->attachment)) { if (empty($eventdata->attachname)) { // Attachment needs a file name. debugging('Attachments should have a file name. No attachments have been sent.', DEBUG_DEVELOPER); } else if (!($eventdata->attachment instanceof stored_file)) { // Attachment should be of a type stored_file. debugging('Attachments should be of type stored_file. No attachments have been sent.', DEBUG_DEVELOPER); } else { // Copy attachment file to a temporary directory and get the file path. $attachment = $eventdata->attachment->copy_content_to_temp(); // Get attachment file name. $attachname = clean_filename($eventdata->attachname); } } // Configure mail replies - this is used for incoming mail replies. $replyto = ''; $replytoname = ''; if (isset($eventdata->replyto)) { $replyto = $eventdata->replyto; if (isset($eventdata->replytoname)) { $replytoname = $eventdata->replytoname; } } // We email messages from private conversations straight away, but for group we add them to a table to be sent later. $emailuser = true; if (!$eventdata->notification) { if ($eventdata->conversationtype == \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP) { $emailuser = false; } } if ($emailuser) { $result = email_to_user($recipient, $eventdata->userfrom, $eventdata->subject, $eventdata->fullmessage, $eventdata->fullmessagehtml, $attachment, $attachname, true, $replyto, $replytoname); } else { $messagetosend = new stdClass(); $messagetosend->useridfrom = $eventdata->userfrom->id; $messagetosend->useridto = $recipient->id; $messagetosend->conversationid = $eventdata->convid; $messagetosend->messageid = $eventdata->savedmessageid; $result = $DB->insert_record('message_email_messages', $messagetosend, false); } // Remove an attachment file if any. if (!empty($attachment) && file_exists($attachment)) { unlink($attachment); } return $result; } /** * Creates necessary fields in the messaging config form. * * @param array $preferences An array of user preferences */ function config_form($preferences){ global $USER, $OUTPUT, $CFG; $string = ''; $choices = array(); $choices['0'] = get_string('textformat'); $choices['1'] = get_string('htmlformat'); $current = $preferences->mailformat; $string .= $OUTPUT->container(html_writer::label(get_string('emailformat'), 'mailformat')); $string .= $OUTPUT->container(html_writer::select($choices, 'mailformat', $current, false, array('id' => 'mailformat'))); $string .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'userid', 'value' => $USER->id)); if (!empty($CFG->allowusermailcharset)) { $choices = array(); $charsets = get_list_of_charsets(); if (!empty($CFG->sitemailcharset)) { $choices['0'] = get_string('site').' ('.$CFG->sitemailcharset.')'; } else { $choices['0'] = get_string('site').' (UTF-8)'; } $choices = array_merge($choices, $charsets); $current = $preferences->mailcharset; $string .= $OUTPUT->container(html_writer::label(get_string('emailcharset'), 'mailcharset')); $string .= $OUTPUT->container( html_writer::select($choices, 'preference_mailcharset', $current, false, array('id' => 'mailcharset')) ); } if (!empty($CFG->messagingallowemailoverride)) { $inputattributes = array('size' => '30', 'name' => 'email_email', 'value' => $preferences->email_email, 'id' => 'email_email'); $string .= html_writer::label(get_string('email', 'message_email'), 'email_email'); $string .= $OUTPUT->container(html_writer::empty_tag('input', $inputattributes)); if (empty($preferences->email_email) && !empty($preferences->userdefaultemail)) { $string .= $OUTPUT->container(get_string('ifemailleftempty', 'message_email', $preferences->userdefaultemail)); } if (!empty($preferences->email_email) && !validate_email($preferences->email_email)) { $string .= $OUTPUT->container(get_string('invalidemail'), 'error'); } $string .= '
'; } return $string; } /** * Parses the submitted form data and saves it into preferences array. * * @param stdClass $form preferences form class * @param array $preferences preferences array */ function process_form($form, &$preferences){ global $CFG; if (isset($form->email_email)) { $preferences['message_processor_email_email'] = clean_param($form->email_email, PARAM_EMAIL); } if (isset($form->preference_mailcharset)) { $preferences['mailcharset'] = $form->preference_mailcharset; if (!array_key_exists($preferences['mailcharset'], get_list_of_charsets())) { $preferences['mailcharset'] = '0'; } } if (isset($form->mailformat) && isset($form->userid)) { require_once($CFG->dirroot.'/user/lib.php'); $user = core_user::get_user($form->userid, '*', MUST_EXIST); $user->mailformat = clean_param($form->mailformat, PARAM_INT); user_update_user($user, false, false); } } /** * Returns the default message output settings for this output * * @return int The default settings */ public function get_default_messaging_settings() { return MESSAGE_PERMITTED + MESSAGE_DEFAULT_ENABLED; } /** * Loads the config data from database to put on the form during initial form display * * @param array $preferences preferences array * @param int $userid the user id */ function load_data(&$preferences, $userid){ $preferences->email_email = get_user_preferences( 'message_processor_email_email', '', $userid); } /** * Returns true as message can be sent to internal support user. * * @return bool */ public function can_send_to_any_users() { return true; } } PK,B\ I.,,db/services.phpnu[PK,B\hddkdb/upgrade.phpnu[PK,B\sEZeDD lang/en/auth_email.phpnu[PK,B\Z< version.phpnu[PK,B\AFAFaclasses/external.phpnu[PK,B\Qrx`classes/privacy/provider.phpnu[PK,B\aG&& wupgrade.txtnu[PK,B\,f7&7& ytests/external/external_test.phpnu[PK,B\g$$ tests/behat/behat_auth_email.phpnu[PK,B\*ex/22{tests/behat/signup.featurenu[PK,B\\$auth.phpnu[PK,B\4J settings.phpnu[PKS\Fdb/install.phpnu[PKS\[È db/tasks.phpnu[PKS\~wdb/install.xmlnu[PKS\tf udb/events.phpnu[PKS\SR 1lang/en/message_email.phpnu[PKS\mӁMclasses/event_observers.phpnu[PKS\$Abbclasses/output/renderer.phpnu[PKS\ J ?? classes/output/email_digest.phpnu[PKS\C]rr!V#classes/output/email/renderer.phpnu[PKS\e||+)classes/output/email/renderer_textemail.phpnu[PKS\{l<88 .classes/task/send_email_task.phpnu[PKS\*3 $xItemplates/email_digest_text.mustachenu[PKS\$Stemplates/email_digest_html.mustachenu[PKS\ADn gtests/privacy/provider_test.phpnu[PKS\Rttests/task/send_email_test.phpnu[PKS\o3e$$tests/event_observers_test.phpnu[PKS\4%%message_output_email.phpnu[PK ٽ