AutorÃa | Ultima modificación | Ver Log |
<?php// This file is part of the Contact Form plugin for Moodle - https://moodle.org///// Contact Form 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.//// Contact Form 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 Contact Form. If not, see <https://www.gnu.org/licenses/>./*** This plugin for Moodle is used to send emails through a web form.** @package local_contact* @copyright 2016-2024 TNG Consulting Inc. - www.tngconsulting.ca* @author Michael Milette* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*//*** local_contact class. Handles processing of information submitted from a web form.* @copyright 2016-2024 TNG Consulting Inc. - www.tngconsulting.ca* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class local_contact {/*** The name of the sender for the message.** @var string*/public $fromname;/*** The email address of the sender for the message.** @var string*/public $fromemail;/*** True if the information submitted is considered to have been sent from a spambot.** @var bool*/public $isspambot;/*** Error message in case there are any issues.** @var string*/public $errmsg;/*** Class constructor. Receives and validates information received through a* web form submission.** @return boolean True if the information received passes our spambot detection. False if it fails.*/public function __construct() {global $CFG;if (isloggedin() && !isguestuser()) {// If logged-in as non guest, use their registered fullname and email address.global $USER;$this->fromname = get_string('fullnamedisplay', null, $USER);$this->fromemail = $USER->email;// Insert name and email address at first position in $_POST array.if (!empty($_POST['email'])) {unset($_POST['email']);}if (!empty($_POST['name'])) {unset($_POST['name']);}$_POST = array_merge(['email' => $this->fromemail], $_POST);$_POST = array_merge(['name' => $this->fromname], $_POST);} else {// If not logged-in as a user or logged in a guest, the name and email fields are required.if (empty($this->fromname = trim(optional_param(get_string('field-name', 'local_contact'), '', PARAM_TEXT)))) {$this->fromname = required_param('name', PARAM_TEXT);}if (empty($this->fromemail = trim(optional_param(get_string('field-email', 'local_contact'), '', PARAM_EMAIL)))) {$this->fromemail = required_param('email', PARAM_TEXT);}}$this->fromname = trim($this->fromname ?? '');$this->fromemail = trim($this->fromemail ?? '');$this->isspambot = false;$this->errmsg = '';if ($CFG->branch >= 32) {// As of Moodle 3.2, $CFG->emailonlyfromnoreplyaddress has been deprecated.$CFG->emailonlyfromnoreplyaddress = !empty($CFG->noreplyaddress);}// Did someone forget to configure Moodle properly?// Validate Moodle's no-reply email address.if (!empty($CFG->emailonlyfromnoreplyaddress)) {if (!$this->isspambot&& !empty($CFG->emailonlyfromnoreplyaddress)&& $this->isspambot = !validate_email($CFG->noreplyaddress)) {$this->errmsg = 'Moodle no-reply email address is invalid.';if ($CFG->branch >= 32) {$this->errmsg .= ' (<a href="../../admin/settings.php?section=outgoingmailconfig">change</a>)';} else {$this->errmsg .= ' (<a href="../../admin/settings.php?section=messagesettingemail">change</a>)';}}}// Use primary administrators name and email address if support name and email are not defined.$primaryadmin = get_admin();$CFG->supportemail = empty($CFG->supportemail) ? $primaryadmin->email : $CFG->supportemail;$CFG->supportname = empty($CFG->supportname) ? fullname($primaryadmin, true) : $CFG->supportname;// Validate Moodle's support email address.if (!$this->isspambot && $this->isspambot = !validate_email($CFG->supportemail)) {$this->errmsg = 'Moodle support email address is invalid.';$this->errmsg .= ' (<a href="../../admin/settings.php?section=supportcontact">change</a>)';}// START: Spambot detection.// File attachments not supported.$supportattachments = !empty(get_config('local_contact', 'attachment'));if (!$supportattachments && !$this->isspambot && $this->isspambot = !empty($_FILES)) {$this->errmsg = 'File attachments not enabled.';}// Validate submit button.if (!$this->isspambot && $this->isspambot = !isset($_POST['submit'])) {$this->errmsg = 'Missing submit button.';}// Limit maximum number of form $_POST fields to 1024.if (!$this->isspambot) {$postsize = @count($_POST);if ($this->isspambot = ($postsize > 1024)) {$this->errmsg = 'Form cannot contain more than 1024 fields.';} else if ($this->isspambot = ($postsize == 0)) {$this->errmsg = 'Form must be submitted using POST method.';}}// Limit maximum size of allowed form $_POST submission to 256 KB.if (!$this->isspambot) {$postsize = (int) @$_SERVER['CONTENT_LENGTH'];if ($this->isspambot = ($postsize > 262144)) {$this->errmsg = 'Form cannot contain more than 256 KB of data.';}}// Validate if "sesskey" field contains the correct value.if (!$this->isspambot && $this->isspambot = (optional_param('sesskey', '3.1415', PARAM_RAW) != sesskey())) {$this->errmsg = '"sesskey" field is missing or contains an incorrect value.';}// Validate referrer URL.if (!$this->isspambot && $this->isspambot = !isset($_SERVER['HTTP_REFERER'])) {$this->errmsg = 'Missing referrer.';}if (!$this->isspambot && $this->isspambot = (stripos($_SERVER['HTTP_REFERER'], $CFG->wwwroot) != 0)) {$this->errmsg = 'Unknown referrer - must come from this site: ' . $CFG->wwwroot;}// Validate sender's email address.if (!$this->isspambot && $this->isspambot = !validate_email($this->fromemail)) {$this->errmsg = 'Unknown sender - invalid email address or the form field name is incorrect.';}// Validate sender's name.if (!$this->isspambot && $this->isspambot = empty($this->fromname)) {$this->errmsg = 'Missing sender - invalid name or the form field name is incorrect';}// Validate against email address whitelist and blacklist.$skipdomaintest = false;// TODO: MDL-0 - Create a plugin setting for this list.$whitelist = ''; // Future code: $config->whitelistemails .$whitelist = ',' . $whitelist . ',';// TODO: MDL-0 - Create a plugin blacklistemails setting.$blacklist = ''; // Future code: $config->blacklistemails .$blacklist = ',' . $blacklist . ',';if (!$this->isspambot && stripos($whitelist, ',' . $this->fromemail . ',') != false) {$skipdomaintest = true; // Skip the upcoming domain test.} else {if (!$this->isspambot&& $blacklist != ',,'&& $this->isspambot = ($blacklist == '*' || stripos($blacklist, ',' . $this->fromemail . ',') == false)) {// Nice try. We know who you are.$this->errmsg = 'Bad sender - Email address is blacklisted.';}}// Validate against domain whitelist and blacklist... except for the nice people.if (!$skipdomaintest && !$this->isspambot) {// TODO: MDL-0 - Create a plugin whitelistdomains setting.$whitelist = ''; // Future code: $config->whitelistdomains .$whitelist = ',' . $whitelist . ',';$domain = substr(strrchr($this->fromemail, '@'), 1);if (stripos($whitelist, ',' . $domain . ',') != false) {// Ya, you check out. This email domain is gold here!$blacklist = '';} else {// TODO: MDL-0 - Create a plugin blacklistdomains setting.$blacklist = 'example.com,example.net,sample.com,test.com,specified.com'; // Future code:$config->blacklistdomains .$blacklist = ',' . $blacklist . ',';if ($blacklist != ',,'&& $this->isspambot = ($blacklist == '*'|| stripos($blacklist, ',' . $domain . ',') != false)) {// Naughty naughty. We know all about your kind.$this->errmsg = 'Bad sender - Email domain is blacklisted.';}}}// TODO: MDL-0 - Test IP address against blacklist.// END: Spambot detection... Wait, got some photo ID on you? ;-) .}/*** Creates a user info object based on provided parameters.** @param string $email email address.* @param string $name (optional) Plain text real name.* @param int $id (optional) Moodle user ID.** @return object Moodle userinfo.*/private function makeemailuser($email, $name = '', $id = -99) {$emailuser = new stdClass();$emailuser->email = trim(filter_var($email, FILTER_SANITIZE_EMAIL) ?? '');if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {$emailuser->email = '';}$emailuser->firstname = format_text($name, FORMAT_PLAIN, ['trusted' => false]);$emailuser->lastname = '';$emailuser->maildisplay = true;$emailuser->mailformat = 1; // 0 (zero) text-only emails, 1 (one) for HTML emails.$emailuser->id = $id;$emailuser->firstnamephonetic = '';$emailuser->lastnamephonetic = '';$emailuser->middlename = '';$emailuser->alternatename = '';$emailuser->username = '';return $emailuser;}/*** Send email message and optionally autorespond.** @param string $email Recipient's Email address.* @param string $name Recipient's real name in plain text.* @param boolean $sendconfirmationemail Set to true to also send an autorespond confirmation email back to user (TODO).** @return boolean $status - True if message was successfully sent, false if not.*/public function sendmessage($email, $name, $sendconfirmationemail = false) {global $USER, $CFG, $SITE;$systemcontext = context_system::instance();// Create the sender from the submitted name and email address.$from = $this->makeemailuser($this->fromemail, $this->fromname);// Create the recipient.$to = $this->makeemailuser($email, $name);// Create the Subject for message.$subject = '';if (empty(get_config('local_contact', 'nosubjectsitename'))) { // Not checked.// Include site name in subject field.$subject .= '[' . format_string($SITE->shortname, true, ['escape' => false, 'context' => $systemcontext]) . '] ';}$subject .= optional_param(get_string('field-subject', 'local_contact'),get_string('defaultsubject', 'local_contact'),PARAM_TEXT);// Build the body of the email using user-entered information.// Note: Name of message field is defined in the language pack.$fieldmessage = get_string('field-message', 'local_contact');$htmlmessage = '';/*** Callback function for array_filter.** @param string $string Text to be chekced.* @return boolean true if string is not empty, otherwise false.*/function filterempty($string) {$string = trim($string ?? '');return ($string !== null && $string !== false && $string !== '');}foreach ($_POST as $key => $value) {// Only process key conforming to valid form field ID/Name token specifications.if (preg_match('/^[A-Za-z][A-Za-z0-9_:\.-]*/', $key)) {if (is_array($value)) {// Join array of values. Example: <select multiple>.$value = array_filter($value, "filterempty");$value = join(', ', $value);}$value = (!empty($value) ? trim($value) : '');// Exclude fields we don't want in the message and empty fields.if (!in_array($key, ['sesskey', 'submit']) && $value != '') {// Apply minor formatting of the key by replacing underscores with spaces.$key = str_replace('_', ' ', $key);// Make custom alterations.switch ($key) {case 'message':// Message field - use translated value from language file.$key = $fieldmessage;// Continue checking for more issues to fix.case strpos($value, "\n") !== false:// Field contains linefeeds.case $fieldmessage: // Message field.// Strip out excessive empty lines.$value = preg_replace('/\n(\s*\n){2,}/', "\n\n", $value);// Sanitize the text.$value = format_text($value, FORMAT_PLAIN, ['trusted' => false]);// Add to email message.$htmlmessage .= '<p><strong>' . ucfirst($key) . ' :</strong></p><p>' . $value . '</p>';break;// Don't include the following fields in the body of the message.case 'recipient':// Recipient field.case 'recaptcha challenge field':// ReCAPTCHA related field.case 'recaptcha response field':// ReCAPTCHA related field.case 'g-recaptcha-response':// ReCAPTCHA related field.break;// Use language translations for the labels of the following fields.case 'name':// Name field.case 'email':// Email field.case 'subject':// Subject field.$key = get_string('field-' . $key, 'local_contact');// Continue processing.default:// All other fields.// Sanitize the text.$value = format_text($value, FORMAT_PLAIN, ['trusted' => false]);if (filter_var($value, FILTER_VALIDATE_URL)) {// Convert URL into clickable link.$value = '<a href="' . $value . '">' . $value . '</a>';}// Add to email message.$htmlmessage .= '<strong>' . ucfirst($key) . ' :</strong> ' . $value . '<br>' . PHP_EOL;}}}}$attachname = '';$attachpath = '';// If support for an attachement is enabled.$supportattachments = !empty(get_config('local_contact', 'attachment'));if ($supportattachments) {// Take the first file as the attachment.foreach ($_FILES as $value) {$attachname = $value['name'];$path = $CFG->tempdir . '/local_contact/';if (!is_dir($path)) {mkdir($path); // Create temp directory if it does not exist.}$attachpath = tempnam($path, 'attachment_');move_uploaded_file($value['tmp_name'], $attachpath);break;}}// Sanitize user agent and referer.$httpuseragent = format_text($_SERVER['HTTP_USER_AGENT'], FORMAT_PLAIN, ['trusted' => false]);$httpreferer = format_text($_SERVER['HTTP_REFERER'], FORMAT_PLAIN, ['trusted' => false]);// Prepare arrays to handle substitution of embedded tags in the footer.$tags = ['[fromname]','[fromemail]','[supportname]','[supportemail]','[lang]','[userip]','[userstatus]','[sitefullname]','[siteshortname]','[siteurl]','[http_user_agent]','[http_referer]',];$info = [$from->firstname,$from->email,$CFG->supportname,$CFG->supportemail,current_language(),getremoteaddr(),$this->moodleuserstatus($from->email),format_text($SITE->fullname, FORMAT_HTML, ['context' => $systemcontext, 'escape' => false]) . ': ',format_text($SITE->shortname, FORMAT_HTML, ['context' => $systemcontext, 'escape' => false]),$CFG->wwwroot,$httpuseragent,$httpreferer,];// Create the footer - Add some system information.$footmessage = get_string('extrainfo', 'local_contact');$footmessage = format_text($footmessage, FORMAT_HTML, ['trusted' => true, 'noclean' => true, 'para' => false]);$htmlmessage .= str_replace($tags, $info, $footmessage);// Override "from" email address if one was specified in the plugin's settings.$noreplyaddress = $CFG->noreplyaddress;if (!empty($customfrom = get_config('local_contact', 'senderaddress'))) {$CFG->noreplyaddress = $customfrom;}// Send email message to recipient and set replyto to the sender's email address and name.if (empty(get_config('local_contact', 'noreplyto'))) { // Not checked.$status = email_to_user($to,$from,$subject,html_to_text($htmlmessage),$htmlmessage,$attachpath,$attachname,true,$from->email,$from->firstname);} else { // Checked.$status = email_to_user($to, $from, $subject, html_to_text($htmlmessage), $htmlmessage, $attachpath, $attachname, true);}$CFG->noreplyaddress = $noreplyaddress;// If successful and a confirmation email is desired, send it the original sender.if ($status && $sendconfirmationemail) {// Substitute embedded tags for some information.$htmlmessage = str_replace($tags, $info, get_string('confirmationemail', 'local_contact'));$htmlmessage = format_text($htmlmessage, FORMAT_HTML, ['trusted' => true, 'noclean' => true, 'para' => false]);$replyname = empty($CFG->emailonlyfromnoreplyaddress) ? $CFG->supportname : get_string('noreplyname');$replyemail = empty($CFG->emailonlyfromnoreplyaddress) ? $CFG->supportemail : $CFG->noreplyaddress;$to = $this->makeemailuser($replyemail, $replyname);// Send confirmation email message to the sender.email_to_user($from, $to, $subject, html_to_text($htmlmessage), $htmlmessage, '', '', true);}return $status;}/*** Builds a one line status report on the user. Uses their Moodle info, if* logged in, or their email address to look up the information if they are* not.** @param string $emailaddress Plain text email address.** @return string Contains what we know about the Moodle user including whether they are logged in or out.*/private function moodleuserstatus($emailaddress) {if (isloggedin() && !isguestuser()) {global $USER;$info = get_string('fullnamedisplay', null, $USER) . ' / ' . $USER->email . ' (' . $USER->username .' / ' . get_string('eventuserloggedin', 'auth') . ')';} else {global $DB;$usercount = $DB->count_records('user', ['email' => $emailaddress, 'deleted' => 0]);switch ($usercount) {case 0: // We don't know this email address.$info = get_string('emailnotfound');break;case 1: // We found exactly one match.$user = get_complete_user_data('email', $emailaddress);$extrainfo = '';// Is user locked out?if ($lockedout = get_user_preferences('login_lockout', 0, $user)) {$extrainfo .= ' / ' . get_string('lockedout', 'local_contact');}// Has user responded to confirmation email?if (empty($user->confirmed)) {$extrainfo .= ' / ' . get_string('notconfirmed', 'local_contact');}$info = get_string('fullnamedisplay', null, $user) . ' / ' . $user->email . ' (' . $user->username .' / ' . get_string('eventuserloggedout') . $extrainfo . ')';break;default: // We found multiple users with this email address.$info = get_string('duplicateemailaddresses', 'local_contact');}}return $info;}}