Johan Broddfelt
/* Comments on code */
Donate $

Send me an email

Now we have implemented comments, and that is great. But as an administrator I do not want to check my site every day to see if someone has written a comment. I want a mail in my inbox that tells me what has been writte and a link to the post so that I just need to click on it in order to post a reply. For that we need a Mail class. And why not build the complete class right now, because I know I'm going to use it many times from now on. Not only to send mails to my self, but also to send mails to those who want updates on posts they have commented, those who want updates on new posts and I'm also going to build an error logging function that also should send mails to me if things go south.

Here is the Mail class. It has one function for storing the mail in the database for later sending and one function for accually sending the mail. The reason why we do this is because we do not want a page reload to start sending hundreds of mail, because then the site will be really slow. But if we store the mails in the database for later sendeing we not only gain speed, but we also have an excelent log of what mails have been sent.

// classes/Mail.php
<?php
/************************************
 * 
 * Usage of the Mail class
 * 
 * $m = new Mail();
 * $m->to = 'recipiant@mail.com';
 * $m->subject = 'Hello my friend';
 * $m->message = 'Long time no see...';
 * 
 * Optional
 * $m->files[] = "docs/file.txt";
 * $m->embed[] = "graphics/logo.png,logo,logo.png"; // use <img src="cid:logo"> in order to access image
 *
 * $m->registerMail(); // Will register it and send it with the queue
 * or
 * $m->sendMail(); // Will register it and send it directly
 * 
 ************************************/
class Mail extends Db {
	public $from    = 'mail@mydomain.se';
	public $name    = '[My mail name]';
	public $to      = '';
	public $subject = '';
	public $message = '';
	public $files   = array();
        public $embed   = array();
        public $table  = 'mail';
	
    
    function update() {
        $this->subject = Db::real_escape_string($this->subject);
        $this->message = Db::real_escape_string($this->message);
        if (is_array($this->files)) {
            $this->files = Db::real_escape_string(implode('|', $this->files));
            $this->embed = Db::real_escape_string(implode('|', $this->embed));
        }
        parent::update(true);
    }
    
    function registerMail() {
        $this->created = date('Y-m-d H:i:s');
        $this->files = Db::real_escape_string(implode('|', $this->files));
        $this->embed = Db::real_escape_string(implode('|', $this->embed));
        $sql = 'INSERT INTO ' . $this->table . ' ('
                . '`created`, `from`, `to`, `subject`, `message`, `files`, `embed`'
                . ') VALUES ('
                . '"' . $this->created . '", '
                . '"' . Db::real_escape_string($this->from) . '", '
                . '"' . Db::real_escape_string($this->to) . '", '
                . '"' . Db::real_escape_string($this->subject) . '", '
                . '"' . Db::real_escape_string($this->message) . '",'
                . '"' . Db::real_escape_string($this->files) . '",'
                . '"' . Db::real_escape_string($this->embed) . '"'
                . ')';
        //echo $sql . '
';
        Db::query($sql);
        $this->id = Db::insert_id();
        //$this->saveFilesToDisk($this->id);
    }
    
    function getFileNameList() {
        $list = array();
        if (count($this->files) > 0) {
            foreach ($this->files as $file) {
                $list[] = $file;
            }
        }
        return $list;
    }
    
    function saveFilesToDisk($id) {
        if ((int)$id error = 'missing_id_from_mail_log';
        }
        if ($this->files == '') {
            return false;
        }
        $fileList = explode('|', $this->files);

        $docs = 'docs/';
        if (!is_dir($docs)) {
            mkdir($docs);
        }

        $mailAttatchments = $docs . 'mail_attatchments/';
        if (!is_dir($mailAttatchments)) {
            mkdir($mailAttatchments);
        }

        $mailFolder = $mailAttatchments . $id . '/';
        if (!is_dir($mailFolder)) {
            mkdir($mailFolder);
        }
        
        foreach ($fileList as $file) {
            $filePath = $mailFolder . md5($file['name']);
            $handle = fopen($filePath, 'w');
            fwrite($handle, $file['document']);
            fclose($handle);
        }
    }
    
    function sendMail() {
        if ($this->id == 0) {
            $this->registerMail();
        } else {
            $this->files = Db::real_escape_string(implode('|', $this->files));
            $this->embed = Db::real_escape_string(implode('|', $this->embed));
        }
        $textMessage = strip_tags($this->message);
        #echo $textMessage.'';
        $htmlMessage = $this->message;
        #echo $htmlMessage.'';
        if ($textMessage == $htmlMessage) {
            $htmlMessage = String::textToHTML($this->message);
        }
        
        $mail = new PHPMailer;
        $mail->CharSet = 'UTF-8';
        $mail->Encoding = 'base64';

        #$mail->isSMTP();                                      // Set mailer to use SMTP
        #$mail->Host = 'smtp1.example.com;smtp2.example.com';  // Specify main and backup SMTP servers
        #$mail->SMTPAuth = true;                               // Enable SMTP authentication
        #$mail->Username = 'user@example.com';                 // SMTP username
        #$mail->Password = 'secret';                           // SMTP password
        #$mail->SMTPSecure = 'tls';                            // Enable TLS encryption, `ssl` also accepted
        #$mail->Port = 587;                                    // TCP port to connect to

        $mail->setFrom($this->from);
        $mail->addAddress($this->to);     // Add a recipient
        #$mail->addAddress('test@example.com', 'Testing');               // Name is optional
        #$mail->addReplyTo('info@example.com', 'Information');
        #$mail->addCC('cc@example.com');
        #$mail->addBCC('bcc@example.com');

        if ($this->files != '') {
            $fileList = explode('|', $this->files);
            foreach ($fileList as $file) {
                $mail->addAttachment($file);
            }
        }
        if ($this->embed != '') {
            $embedList = explode('|', $this->embed);
            $embedArr = array();
            foreach ($embedList as $em) {
                $embedArr = explode(',', $em);
                $mail->AddEmbeddedImage($embedArr[0], $embedArr[1], $embedArr[2]);
            }
        }

        $mail->isHTML(true);                            // Set email format to HTML

        $mail->Subject = $this->subject;
        $mail->Body    = $htmlMessage;
        $mail->AltBody = $textMessage;

        $Settings = Settings::getInstance();
        $sendMail = $Settings->get('send_mail');
        $sent = false;
        if ($sendMail) {
            $sent = $mail->send();
        } else {
            $sent = true;
        }
        if ($sent) {
            $m = new Mail((int)$this->id);
            $m->sent = date('Y-m-d H:i:s');
            $m->update();
        }
        return $sent;
    }
}

WARNING! Previously I created the mime header and message by my self and then used the standard mail() function in php. That is a bad idea. Use a library like PHPMailer. The reason is that your mime might work in one server but as I discovered there were some issues when deploying the same script in another environment. And there are a lot of specific things to care for like where to place extra newlines and how to encode your message and other stuff. And you just don't want to spend time debugging and scratching your head with that. In this case I just downloaded the class.PHPMailer.php, renamed it to PHPMailer.php and placed it in my classes folder.

As I said, we do not only send mail here, we also save them to the database, so we need a table.

CREATE TABLE IF NOT EXISTS `mail` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `from` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_swedish_ci NOT NULL,
  `to` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_swedish_ci NOT NULL,
  `subject` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_swedish_ci NOT NULL,
  `message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_swedish_ci NOT NULL,
  `files` text CHARACTER SET utf8mb4 COLLATE utf8mb4_swedish_ci NOT NULL,
  `embed` text CHARACTER SET utf8mb4 COLLATE utf8mb4_swedish_ci NOT NULL,
  `created` datetime NOT NULL,
  `sent` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `sent` (`sent`),
  KEY `to` (`to`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=1;

As you can see we added a parameter to the update function called $sequre. This means that the fields sent to the update are sequred for SQL- injection and should not pass through the regular security. So we need to make that update in our Db.php in the update function

// classes/Db.php
    function update($sequre=false) {
        $fields = $this->fetchFields();
        foreach ($fields as $field) {
            $columnName[] = $field->columnName;
            $fieldName    = $this->strToCamel($field->columnName);
            if (!$sequre) {
                $data[]       = $this->handleDataIn($field->columnName, $field->columnType, $this->$fieldName);
            } else {
                $data[]       = $this->$fieldName;
            }
        }
 ...

We also have a function called String::textToHTML() that converts raw text to html, if the text is not html to begin with. So we add that in the String class. And we also include the separate function fixLinks() that will turn a url into a clickable link.

// classes/String.php
    function textToHTML($text) {
        // Change 
, 

, 
 and 
 to <br>
        $html = nl2br($text, false);
        // Turn url:s in to clickable links
        $html = String::fixLinks($html);
        return $html;
    }
    
    function fixLinks($text) {
        // Remove all wbr chars from the document so we do not create duplicates later
        $text = preg_replace('/​/s', '', $text);

        // add target blank to all links
        $text = preg_replace('/(<a.*?href="([^"]*?)".*?>)/', '<a href="$2" target="_blank">', stripslashes($text));

        // Find link without a-tag and add a-tags to it
        $text = preg_replace('~([$|s])(http[s]?://[^s|^<]+)~s', '$1<a href="$2" target="_blank">$2</a>', $text);
        
        // Add wbr to all / & and ? in ancor text so that long links will wrap in the page if it get's to cramped
        $strArr = str_split($text);
        $open = true;
        $newStr = '';
        $i = 0;
        foreach ($strArr as $char) {
            unset($p);
            $p = ' ';
            if ($i > 0) {
                $p = $strArr[$i-1];
            }
            unset($n);
            $n = '';
            if (isset($strArr[$i+1])) {
                $n = $strArr[$i+1];
            }
            if ($char == '<') {
                $open = false;
            }
            if ($char == '>') {
                $open = true;
            }
            if ($open 
                and ($char == '/' or $char == '?' or $char == '=' or $char == '.')
                and ($p != ' ' and $p != '/' and $p != '=' and $p != '.' and isset($p))
                and ($n != ' ' and $n != '/' and $n != '=' and $n != '.' and isset($n))
                ) {
                $newStr .= $char . '​';
            } else {
                $newStr .= $char;
            }
            $i++;
        }
        return $newStr;
    }

Settings

In order to disable mail to be sent i our dev or demo environment, we add a Settings class. This will later be used for storing all kinds of settings for your system.

// classes/Settings.php
<?php
/********************************************
 *
 * Settings is a Singelton
 * There can only be one settings array that is active in one session    
 *      
 ********************************************/  

class Settings extends Db {
    public $setting = array();
    public $table = 'settings';
    
    function &getInstance() {
        static $obj;
        if (!isset($obj)) {
            $v = new Settings;
            $obj = $v;
        }
        return $obj;
    }

    function get($param) {
         return $this->setting[$param];
    }

    function set($param, $value) {
         $this->setting[$param] = $value;
         return true;
    }
    
    function loadSettings() {
        $arr = $this->fetchArray();
        foreach ($arr as $setting) {
            $this->set(trim($setting->key), trim($setting->value));
        }
    }
}

In order to be able to use the Settings we need to add the following lines to our global.php. They will load the Settings into the Settings object.

// Load settings
$settings = Settings::getInstance();
$settings->loadSettings();

I normaly use a text file for keeping settings. But in this installation I actually choose to store the settings in a databse table. So here you go.

CREATE TABLE IF NOT EXISTS `settings` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `key` varchar(50) COLLATE utf8mb4_bin NOT NULL,
  `value` varchar(50) COLLATE utf8mb4_bin NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=2 ;

INSERT INTO `settings` (`id`, `key`, `value`) VALUES
(1, 'send_mail', '0');

Now the only thing remaining is to implement the Mail call when adding a comment. We do this by adding the following code in the same if-case where we update the comment, like this.

// views/comment/add.php
        $m = new Mail();
        $m->to = 'you.mail@your.domain';
        $m->subject = 'New comment at "' . $obj->title . '"';
        $m->message = $com->name . ' (' . $com->mail . ') skriver:' . PHP_EOL  . PHP_EOL . $com->comment . PHP_EOL  . PHP_EOL . 'Link ' . 

Http::currentURL();
        $m->sendMail();

And in order to include a back link, for easy access, in the mail. We call Http::currentURL() that looks like this.

// classes/Http.php
    function currentURL($part='full') {
        $pageURL = '';

        $http = 'http';
        if ($_SERVER["HTTPS"] == "on") { $http .= "s"; }
        $http .= '://';

        $port = '';
        if ($_SERVER["SERVER_PORT"] != "80") {
            $port = ':' . $_SERVER["SERVER_PORT"];
        }

        $pageURL .= $http . $_SERVER["SERVER_NAME"] . $port . $_SERVER["REQUEST_URI"];

        if ($part == 'path') {
            $pageURL = preg_replace("/(https?://(.+?/))[^/]+?.[^/]+?$/i", "$1", $pageURL);
        }
        if ($part == 'base') {
            $pageURL = preg_replace("/(https?://[^/]+?)[/|$].*?$/i", "$1", $pageURL);
        }

        return $pageURL;
    }

IMPORTANT! When creating a mail service always make sure that you have a spf1 record in your DNS. Otherwise you run a high risk ending up in the spam-filter.

- Framework, PHP, Mail

<< Give your audience a voice Build a mailinglist >>

Comment

Name
Mail (Not public)
Send mail uppdates on new comments

Comments

0 post found