Archive

Archive for the ‘Computing’ Category

A few useful regular expressions

January 16th, 2009 No comments

Here are a few useful regular expressions I’ve collected. I will append regular expressions to this list whenever I find a useful new one.
If you run windows, check out http://www.weitz.de/regex-coach/. It’s a very useful application to write and test regular expressions. If you know a good linux variant of this program, please comment on this article.

E-mail:
 ^[0-9a-z]([-_.]?[0-9a-z])*@[0-9a-z]([-.]?[0-9a-z])*\.[a-z]{2,4}$

IP address:
^(?:(?:25[0-5]|2[0-4]\d|[01]\d\d|\d?\d)(?(?=\.?\d)\.)){4}$

MySQL timestamp (leap year from 1901 to 2099):
^((\d{2}(([02468][048])|([13579][26]))[\-\/\s]?((((0?[13578])|(1[02]))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\-\/\s]?((0?[1-9])|([1-2][0-9])))))|(\d{2}(([02468][1235679])|([13579][01345789]))[\-\/\s]?((((0?[13578])|(1[02]))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\-\/\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))(\s(((0?[1-9])|(1[0-2]))\:([0-5][0-9])((\s)|(\:([0-5][0-9])))))?$

Dutch (local) phone numbers:
((^06((\s{0,1})|(\-{0,1}))[0-9]{8}$)|(^[0-9]{3,4}(\s{0,1}|\-{0,1})[0-9]{6,7}$)|(^\+{1}[0-9]{2}(\s{0,1}|\-{0,1})[0-9]{2,3}(\s{0,1}|\-{0,1})[0-9]{6,7}$)) 

International phone number:
^(\(?\+?[0-9]*\)?)?[0-9_\- \(\)]*$

Dutch zip/postal code (with or without space):
^[0-9]{4}\s{0,1}[a-zA-z]{2}$

Updating EOL fedora installs with the REMI repositories

January 12th, 2009 No comments

I have multiple fedora (core) linux servers under my management. The problem with the short release cycles of this non-enterprise operating system is the update support with short maintenance periods. End of life is far away for a desktop computer, but the lifetime is pretty short for a server. You don’t want to reinstall/update the operating system on your server every half or whole year. So that is a problem.

But, there is a solution I’ve found on the web. You can get updates for older fedora versions through a repository that is maintained by a 3rd party. I’m very happy that someone is willing to put the effort in those repositories to keep our servers up-to-date even after the EOL date for the fedora operating system has passed.

You can add the repository manually, or you can just install the right rpm installer for your operating system. It is as easy as running wget http://rpms.famillecollet.com/remi-release-[your release number].rpm; rpm -Uvh remi-release-[your release number].rpm from a terminal.

You can also find repositories for enterprise linux 4 & 5 there.

The urls to the repository locations are listed below:
The repositories: http://rpms.famillecollet.com/
The SPEC files used to build the RPM’s: http://rpms.famillecollet.com/SPEC/
 The RPM source files: http://rpms.famillecollet.com/SRPMS/

The repositories manual can be found here: http://blog.famillecollet.com/pages/Config-en

Thank you REMI!

Useful linux console system monitoring commands

December 9th, 2008 No comments

There are lots of commands available to manage a linux computer. So many in fact, that I decided to make this list as a reference for future use. I hope this overview will help some other people too.

Not all of these commands are included in the standard installation of linux. If you need them install them through the package manager of your distro. Most of the included images are from a barely loaded webserver, and some are from a medium loaded webserver. 

top
Top is the command I use most to check the current status on my machines. It shows a screen most like the processes list in the windows task manager. In the header it displays the current system time, system up-time, number of logged in users, load averages, number of processes and their status, physical memory usage and swap memory usage. You can change the refresh interval from the default 3 seconds by pressing “s”. The top command defaults to sorting by CPU usage. Press “F” to change the order of the rows. You have to select the column you want to sort by pressing its associated letter from the table that pops up after pressing “F”. To add or remove columns, press “f”. There are many more useful options for the top screen. For example the option to kill processes, or re-nice them to a different priority. Exit top by pressing “q”. To see al the available options, press “h”. 

The default screen of the top command.

The default screen of the top command.

 

 

iostat
Iostat gives you vital info about the filesystem usage. It also gives some info about CPU usage. “iostat -d” will display only the device report in kilobytes. To run this command in interval, add the time between reports in seconds and optionally how many reports (iostat -dk 3 10, 10 reports at 3 second intervals).  It will continu unhindered if you do not specify the number of reports. Exit iostat with “ctrl-c” in that case.

If you have network filesystem shares (NFS) you can check their statistics with “iostat -n”. All speeds are in blocks/second. These blocks represent sectors on the harddrive. To view the statistics in kilobytes/second or megabytes/second use the options “k” and “m” respectively.

iotop
Iotop is a top like application to show i/o usage. This is very useful when you want to look up which processes put the most load on the filesystem. The “left en right arrow keys” can be used to selected the sorting column. To change the ordering from ascending to descending and back use the “r” key. Exit iotop with the “q” key.

The default iotop screen.

The default iotop screen.

 

 

 

 

 

ps
Shows a list of currently running processes. I mostly use this command to easily look up processes by piping the output of ps to grep. I usually use “ps -ef” to give me full overview using the standard syntax. To look up a process use “ps -ef | grep <process name>”. More about redirecting output from stdout to a different place can be found in my post Basic linux administration with the console (which I’m currently writing so the link may be broken).

sar
Sar is a versatile system management application. It is a front end to the activity counters in linux and it can also read from files. I don’t have experience with sar so I’ll leave this one for you to find out.

vmstat
Vmstat gives you info about the virtual memory (processes, memory, paging, block IO, traps, and cpu activity). To run it at an interval, use “vmstat <delay in seconds> <optional number of reports>. Exit with “ctrl+c”.

sa
Statistics about previously executed commands. This can be accessed by command name, or by username. This command needs to be initialized to record and summarize the commands first. I also don’t use this one so I’ll leave it at that.

apachetop
Apachetop is a command useful for keeping an eye on apache requests and transfer speeds. It watches the apache access log to generate its statistics. You can specify a different logfile with “apachetop -f <logfile>”. You can specify multiple -y options to watch multiple logfiles at once. Exit by pressing “q”.

The apachetop statistics screen.

The apachetop statistics screen.

mytop
Mytop is a top variant to check the processlist of your MySQL server. Unfortunately it cannot accept a password through a password input line like the mysql commands do, but you have to specify it on the commandline. Make sure you delete your command line history with “history -c” after using this command. To use mytop use this command: “mytop -d <database> -u <user> -p <password>”.

The default mytop screen.

The default mytop screen.

mysqlreport
Mysqlreport is a very useful mysql server optimization tool. It analyzes the server and outputs a detailed report with which you can check or improve on the servers health. The command can be downloaded from hackmysql.  The manual for this tool, and what all the values mean can be found in their guide.

iftop
Iftop is a top variant to monitor the network interfaces. It displays al connections and current bandwidth. DNS name resolving can be disabled by using the “-n” option, or pressing the “r” key in iftop. To specify a different interface use the “-i <interfacename, eth0 for example>” option. You can exit iftop with the “q” key.

The iftop default console.

The iftop default console.

mpstat
This command shows info about all the cores and/or processors in the machine.  You can show a combines status report, or by each core/processor seperately. It gives a bit more depth of information than the other commands. To put this command in an interval use “mpstat <optional time between reports in seconds>”. This shows all cores/processors. If you want to display a single core or processor use “mpstat -P <core/processor number> <optional interval in seconds>”. Press “ctrl+c” to exit mpstat.

The default mpstat console screen with an interval of 1 second.

The default mpstat console screen with an interval of 1 second.

free
Free displays the amount of free and used memory. I don’t think this one is particularly useful because this info can be found in other statistical commands too. It displays the values in kilobytes by default. To run this report with an interval use the command “free -s <interval in seconds>”. Again, exit with “ctrl-c”.

lsof
Lsof will show a list of open files. This command is simmilar to ps.

df
Df shows disc usage for al partitions that are currently mounted. To show the values in human readable form, use “df -h”

pstree
Shows the process tree with all parent and child processes.

tload
I rarely use tload, it shows the current load avarages. Top will do just fine for this purpose.

Unserializing stored sessions from Zend_Session

November 27th, 2008 No comments

Do you need to unserialize session data stored in your database with Zend_Session_SaveHandler_DbTable? This can be usefull when you want to build a session management interface in your CMS for example. Zend_Session stores its session data by storing it in a string in this format:

SESSION_NAMESPACE|[serialized_data]SESSION_NAMESPACE|[serialized_data] etc. etc.

Zend Framework does not supply a way to unserialize this data for use other than using the stored session as session. The php function unserialize() can’t cope with the stored format because multiple serialized strings stored are in the data column. session_decode() does work, but restores the session in $_SESSION instead of only unserializing its content and passing it on. To unserialize a stored session in zend framework you can use this method:

    /**
     * Unserializes a stored Zend_Session_SaveHandler_DbTable data column.
     *
     * @param string $data Zend_Session Serialized session namespaces string.
     * @return mixed (array with namespaces and unserialized objects previously stored in session)
     */
    function unserializeZendDbStoredSession($data) {
        $vars = preg_split('/([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff^|]*)\|/', $data,-1,PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
        $numElements = count($vars);
        for($i=0; $numElements > $i && $vars[$i]; $i++) {
            $result[$vars[$i]]=unserialize($vars[++$i]);
        }
        return $result;
    }

With this data you can check who is logged in, for how long, and what they are doing depending off course on the things you store in the session. You can also end sessions by deleting their rows in the session table. Very useful.
Isn’t this unsafe? I think it depends on how you use the data. It can’t alter the session itself at least. Please correct me if I’m wrong.

Compressing and decompressing commands in the linux shell

November 26th, 2008 No comments

Here are a few of the compress and decompress commands available for linux in the shell. I always tend to forget all those parameters. So it’s good to have a quick reference for these commands instead of having to look at the man pages every time.

You can use the extra parameter “v” with all commands with the tar executable. This turns on verbose mode so tar will output what it is doing to stdout.

Wikipedia is a good place to start if you want to know how data compression works. Howstuffworks also has an article about data compression

.tar
Decompress: tar xf [filename].tar
Compress: tar cf [filename].tar [dirname]

.gz
Decompress1: gunzip [filename].gz
Decompress2: gzip -d [filename].gz
Compress: gzip [filename]

.tar.gz
Decompress: tar zxf [filename].tar.gz
Compress: tar zcf [filename].tar.gz [dirname/filename]

.bz2
Decompress1: bzip2 -d [filename].bz2
Decompress2: bunzip2 [filename].bz2
Compress: bzip2 -z [filename]

.tar.bz2
Decompress: tar jxf [filename].tar.bz2
Compress: tar jcf [filename].tar.bz2 [dirname]

.bz
Decompress1: bzip2 -d [filename].bz
Decompress2: bunzip2 [filename].bz

.tar.bz
Decompress: tar jxf [filename].tar.bz

.Z
Decompress: uncompress [filename].Z
Compresscompress [filename]

.tar.z
Decompress: tar Zxf [filename].tar.z
Compress: tar Zcf [filename].tar.z [dirname]

.tgz
Decompress: tar zxf [filename].tgz

.tar.tgz
Decompress: tar zxf [filename].tar.tgz
Compress: tar zcf [filename].tar.tgz [filename]

.zip
Decompress: unzip [filename].zip
Compress: zip [filename].zip  [filenames] (*)

Extending Zend_Mail to redirect test e-mails

November 24th, 2008 3 comments

Have you ever wanted to redirect e-mails sent by a php application to a test e-mail address instead of to the actual “to” address? This can be helpful when running tests or while the application is in development mode. Read on where I explain how to accomplish this in Zend Framework.

Why?
It can be useful to redirect the e-mails your application sends when the application uses, for example, e-mail addresses from the database to notify users of updated articles. In this case you’ll have to change the e-mail addresses in the database if you want to know if the e-mails are actually sent out successfully. But then there is no way to check if the recipient e-mail addresses are the correct e-mail addresses for that particular e-mail.

There is an easier way to do these tests. This way it will be entirely transparent when you use the Zend_Mail object to send out e-mails. Zend Framework is easily adaptable to different scenarios because of the use of OOP. We are going to use this to extend the Zend_Mail object to capture our e-mails before they are sent and redirect them to a single e-mail address when development mode or test mode is enabled.

How?
First off, we are going to create a directory in the library path of the Zend Framework application. I myself named this directory ZendC. It stands for Zend Custom just like the ZendX directory stands for Zend Extras.

We will create a file in this directory with the same name of the original Zend_Mail file (/Zend/Mail.php). Like with all class definitions in Zend Framework, the class names are used to determine the path of the file and the filename for that class. Thus our new mail class should be named ZendC_Mail. When you create a new instance of ZendC_Mail, Zend Framework (with Zend_Loader’s autoload option enabled) will include the file in the path specified by the first part of the class name. The last part will be the filename. /ZendC/Mail.php in this case. ZendC could of course be renamed to anything you would like. Just remember to change both the directory structure in the include path and de class name in the php file to reflect that change.

So we begin with this line of code to extend Zend_Mail:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
/**
 * This object redirects e-mails if development or test mode is enabled so customers are not bothered with test e-mails.
 * It should always be used instead of Zend_Mail where e-mails should be redirected to the testing address.
 * The most current known working version with which this object was tested is Zend Framework 1.7.0.
 * 
 * @version 1.0 
 * @uses Zend_Mail
 */
class ZendC_Mail extends Zend_Mail
{
     // ...

The next thing we will have to do is declare all the private variables that we are going to use internally in our object.

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    /**
     * Global registry.
     * @var Zend_Registry
     */
    private $_registry;
    /**
     * Contains an array with all recipients.
     * @var array (of strings)
     */
    private $_tempRecipients;
    /**
     * Contains an array with original 'To' recipients when in test mode.
     * @var array (of strings)
     */
    private $_tempTo;
    /**
     * Contains an array with original 'Cc' recipients when in test mode.
     * @var mixed
     */
    private $_TempCc;
    /**
     * Contains an array with original 'Bcc' recipients when in test mode.
     * @var mixed
     */
    private $_TempBcc;
    /**
     * Contains a boolean value, is this enviroment test or not.
     * @var boolean
     */
    private $_devMode;

The next thing we will have to do is overload the Zend_Mail constructor. This way we can check if development mode or test mode is enabled and set the $this->_devMode variable so we can use it later to check if it should customize the method’s statements, or just use the parent class method.
I use an instance of Zend_Registry to store if the application is in development mode or not. I also use a variable to store the current enviroment mode (test for PHPUnit tests, production for the production version). This is used in the Initializer of my Zend Framework application. This is not in the scope of this article so I will leave it there. You can customize this constructor to set $this->_devMode according to your own way of implementing different enviroments.

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
    /**
     * Public constructor
     *
     * @param string $charset
     */
    public function __construct ($charset = 'iso-8859-1')
    {
        // Set charset for this e-mail
        $this->_charset = $charset;
        // Get registry from Initializer.
        $this->_registry = Zend_Controller_Front::getInstance()->getPlugin('Initializer')->getRegistry();
        // Get the current enviroment mode.
        $env = Zend_Controller_Front::getInstance()->getPlugin('Initializer')->getEnv();
        // Set addTo to test address if development mode is enabled, or enviroment is not production.
        if ($this->_registry->developmentConfig->developmentMode || $env != 'production') {
            $this->_devMode = true;
            parent::addTo($this->_registry->developmentConfig->emailTestEmail, 'TEST recipient');
        } else {
            $this->_devMode = false;
        }
    }

Next up is overloading the parent class’s setter methods. We begin with the methods to set the recipients of the e-mail.

The first thing it will do is check wether devMode is enabled. It will execute the devMode specific logic when it is, and the parent class’s method if it is disabled.
All of these methods check if a body has been set when devMode is enabled, and throws an exeption if the body is already set. This is important because the body setter methods will add the original recipients to the body of the e-mail. And we don’t want to miss any addresses there. So always set all your recipients before you set the body of the e-mail.

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
    /**
     * Adds To-header and recipient
     *
     * @param  string $name
     * @param  string $email
     * @return Zend_Mail Provides fluent interface
     */
    public function addTo ($email, $name = '')
    {
        // Do nothing if development mode is enabled
        if ($this->_devMode) {
            if (parent::getBodyText() || parent::getBodyHtml()) {
                // Throw error if the body is already set before adding all recipients.
                // All recipients should be known in dev mode before body is set so no e-mail adrresses are omitted
                // in _tempRecipients for addition to the body text/hmtl later.
                throw new Zend_Mail_Exception('Error: Should set e-mail body after adding ALL the recipients!');
            }
            if ($name != '') {
                $this->_TempTo[] = $name . ' <' . $email . '>';
            } else {
                $this->_TempTo[] = $email;
            }
            $this->_tempRecipients[] = $email;
            return $this;
        } else {
            return parent::addTo($email, $name);
        }
    }
    /**
     * Adds Cc-header and recipient
     *
     * @param  string    $name
     * @param  string    $email
     * @return Zend_Mail Provides fluent interface
     */
    public function addCc ($email, $name = '')
    {
        // Do nothing if development mode is enabled
        if ($this->_devMode) {
            if (parent::getBodyText() || parent::getBodyHtml()) {
                // Throw error if the body is already set before adding all recipients.
                // All recipients should be known in dev mode before body is set so no e-mail adrresses are omitted
                // in _tempRecipients for addition to the body text/hmtl later.
                throw new Zend_Mail_Exception('Error: Should set e-mail body after adding ALL the recipients!');
            }
            if ($name != '') {
                $this->_TempCc[] = $name . ' <' . $email . '>';
            } else {
                $this->_TempCc[] = $email;
            }
            $this->_tempRecipients[] = $email;
            return $this;
        } else {
            return parent::addCc($email, $name);
        }
    }
    /**
     * Adds Bcc recipient
     *
     * @param  string    $email
     * @return Zend_Mail Provides fluent interface
     */
    public function addBcc ($email)
    {
        // Do nothing if development mode is enabled
        if ($this->_devMode) {
            if (parent::getBodyText() || parent::getBodyHtml()) {
                // Throw error if the body is already set before adding all recipients.
                // All recipients should be known in dev mode before body is set so no e-mail adrresses are omitted
                // in _tempRecipients for addition to the body text/hmtl later.
                throw new Zend_Mail_Exception('Error: Should set e-mail body after adding ALL the recipients!');
            }
            $this->_TempBcc[] = $email;
            $this->_tempRecipients[] = $email;
            return $this;
        } else {
            return parent::addBcc($email);
        }
    }

The next bit is commented out. It can be enabled again if you want the getRecipients() method to return the test e-mail address (the real recipient when devMode is enabled) instead of the original recipient addresses.

143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
    // Disable custom, always return actual recipients not original in testmode.
    // Other application logic could use these and fail because the method returns the test address instead of the real addresses.
    // But it could be usefull in some cases so I wrote the method, but commented it out.
    //    /**
    //     * Return list of recipient email addresses
    //     *
    //     * @return array (of strings)
    //     */
    //    public function getRecipients()
    //    {
    //        // Do nothing if development mode is enabled
    //        if($this->_devMode) {
    //            return $this->_tempRecipients;
    //        } else {
    //            return parent::getRecipients();
    //        }
    //    }

Next, we will overload the parent class’s body setter methods for both text and html messages.
These methods should always be called after setting all the recipients. They will throw an exception if no recipients have been set yet for this reason.
They will just add the original recipients to the body if devMode is enabled.

160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
    /**
     * Sets the text body for the message.
     *
     * @param  string $txt
     * @param  string $charset
     * @param  string $encoding
     * @return Zend_Mail Provides fluent interface
     */
    public function setBodyText ($txt, $charset = null, $encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE)
    {
        if ($this->_devMode) {
            if (count($this->_tempRecipients) == 0) {
                // Throw error if the body is set before adding all recipients.
                // All recipients should be known in dev mode before body is set.
                throw new Zend_Mail_Exception('Error: Should set e-mail body only after adding ALL the recipients!');
            }
            $txt = "Orginal reciepients:\n" . implode("\n", $this->_tempRecipients) . "\n\nOriginal text:\n" . $txt;
            return parent::setBodyText($txt, $charset, $encoding);
        } else {
            return parent::setBodyText($txt, $charset, $encoding);
        }
    }
    /**
     * Sets the HTML body for the message
     *
     * @param  string    $html
     * @param  string    $charset
     * @param  string    $encoding
     * @return Zend_Mail Provides fluent interface
     */
    public function setBodyHtml ($html, $charset = null, $encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE)
    {
        if ($this->_devMode) {
            if (count($this->_tempRecipients) == 0) {
                // Throw error if the body is set before adding all recipients.
                // All recipients should be known in dev mode before body is set.
                throw new Zend_Mail_Exception('Error: Should set e-mail body only after adding ALL the recipients!');
            }
            $html = 'Orginal reciepients:<br/>' . implode('<br/>', $this->_tempRecipients) . '<br/><br/>Original text:<br/>' . $html;
            return parent::setBodyHtml($html, $charset, $encoding);
        } else {
            return parent::setBodyHtml($html, $charset, $encoding);
        }
    }

The last thing we have to modify is the subject of the e-mail. I just added the word TEST to it to make the test e-mails easy to recognize and filter in my inbox.

204
205
206
207
208
209
210
211
212
213
214
215
216
    /**
     * Sets the subject of the message
     *
     * @param  string    $subject
     * @return Zend_Mail Provides fluent interface
     */
    public function setSubject ($subject)
    {
        if ($this->_devMode) {
            $subject = 'TEST ' . $subject;
        }
        return parent::setSubject($subject);
    }

And we shouldn’t forget to end the class.

217
}

Usage of the ZendC_Mail object
Using this new object is just as easy as using the Zend_Mail version. In fact, it behaves exactly the same because it just forwards all the methods to it’s parent, or does exactly the same in the case of the constructor.
The only difference is that it will send all e-mails to a test address instead of real addresses.

Usage example:

1
2
3
4
5
6
7
$content = 'Content of the e-mail';
$mail = new ZendC_Mail();
$mail->addTo('to@address.com', 'name');
$mail->setFrom('from@address.com', 'name');
$mail->setSubject('Subject');
$mail->setBodyText($content);
$mail->send();

Conclusion
This custom extension of Zend_Mail can be very helpful when your application is in development or testing mode when running PHPUnit tests on your application for example. This is also an example of how easy it is to add your own functionality to the Zend Framework library.

Here is the full /ZendC/Mail.php file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
<?php
/**
 * This object redirects e-mails if development or test mode is enabled so customers are not bothered with test e-mails.
 * It should always be used instead of Zend_Mail where e-mails should be redirected to the testing address.
 * The most current known working version with which this object was tested is Zend Framework 1.7.0.
 * 
 * @version 1.0 
 * @uses Zend_Mail
 */
class ZendC_Mail extends Zend_Mail
{
    /**
     * Global registry.
     * @var Zend_Registry
     */
    private $_registry;
    /**
     * Contains an array with all recipients.
     * @var array (of strings)
     */
    private $_tempRecipients;
    /**
     * Contains an array with original 'To' recipients when in test mode.
     * @var array (of strings)
     */
    private $_tempTo;
    /**
     * Contains an array with original 'Cc' recipients when in test mode.
     * @var mixed
     */
    private $_TempCc;
    /**
     * Contains an array with original 'Bcc' recipients when in test mode.
     * @var mixed
     */
    private $_TempBcc;
    /**
     * Contains a boolean value, is this enviroment test or not.
     * @var boolean
     */
    private $_devMode;
    /**
     * Public constructor
     *
     * @param string $charset
     */
    public function __construct ($charset = 'iso-8859-1')
    {
        // Set charset for this e-mail
        $this->_charset = $charset;
        // Get registry from Initializer.
        $this->_registry = Zend_Controller_Front::getInstance()->getPlugin('Initializer')->getRegistry();
        // Get the current enviroment mode.
        $env = Zend_Controller_Front::getInstance()->getPlugin('Initializer')->getEnv();
        // Set addTo to test address if development mode is enabled, or enviroment is not production.
        if ($this->_registry->developmentConfig->developmentMode || $env != 'production') {
            $this->_devMode = true;
            parent::addTo($this->_registry->developmentConfig->emailTestEmail, 'TEST recipient');
        } else {
            $this->_devMode = false;
        }
    }
    /**
     * Adds To-header and recipient
     *
     * @param  string $name
     * @param  string $email
     * @return Zend_Mail Provides fluent interface
     */
    public function addTo ($email, $name = '')
    {
        // Do nothing if development mode is enabled
        if ($this->_devMode) {
            if (parent::getBodyText() || parent::getBodyHtml()) {
                // Throw error if the body is already set before adding all recipients.
                // All recipients should be known in dev mode before body is set so no e-mail adrresses are omitted
                // in _tempRecipients for addition to the body text/hmtl later.
                throw new Zend_Mail_Exception('Error: Should set e-mail body after adding ALL the recipients!');
            }
            if ($name != '') {
                $this->_TempTo[] = $name . ' <' . $email . '>';
            } else {
                $this->_TempTo[] = $email;
            }
            $this->_tempRecipients[] = $email;
            return $this;
        } else {
            return parent::addTo($email, $name);
        }
    }
    /**
     * Adds Cc-header and recipient
     *
     * @param  string    $name
     * @param  string    $email
     * @return Zend_Mail Provides fluent interface
     */
    public function addCc ($email, $name = '')
    {
        // Do nothing if development mode is enabled
        if ($this->_devMode) {
            if (parent::getBodyText() || parent::getBodyHtml()) {
                // Throw error if the body is already set before adding all recipients.
                // All recipients should be known in dev mode before body is set so no e-mail adrresses are omitted
                // in _tempRecipients for addition to the body text/hmtl later.
                throw new Zend_Mail_Exception('Error: Should set e-mail body after adding ALL the recipients!');
            }
            if ($name != '') {
                $this->_TempCc[] = $name . ' <' . $email . '>';
            } else {
                $this->_TempCc[] = $email;
            }
            $this->_tempRecipients[] = $email;
            return $this;
        } else {
            return parent::addCc($email, $name);
        }
    }
    /**
     * Adds Bcc recipient
     *
     * @param  string    $email
     * @return Zend_Mail Provides fluent interface
     */
    public function addBcc ($email)
    {
        // Do nothing if development mode is enabled
        if ($this->_devMode) {
            if (parent::getBodyText() || parent::getBodyHtml()) {
                // Throw error if the body is already set before adding all recipients.
                // All recipients should be known in dev mode before body is set so no e-mail adrresses are omitted
                // in _tempRecipients for addition to the body text/hmtl later.
                throw new Zend_Mail_Exception('Error: Should set e-mail body after adding ALL the recipients!');
            }
            $this->_TempBcc[] = $email;
            $this->_tempRecipients[] = $email;
            return $this;
        } else {
            return parent::addBcc($email);
        }
    }
    // Disable custom, always return actual recipients not original in testmode.
    // Other application logic could use these and fail because the method returns the test address instead of the real addresses.
    // But it could be usefull in some cases so I wrote the method, but commented it out.
    //    /**
    //     * Return list of recipient email addresses
    //     *
    //     * @return array (of strings)
    //     */
    //    public function getRecipients()
    //    {
    //        // Do nothing if development mode is enabled
    //        if($this->_devMode) {
    //            return $this->_tempRecipients;
    //        } else {
    //            return parent::getRecipients();
    //        }
    //    }
    /**
     * Sets the text body for the message.
     *
     * @param  string $txt
     * @param  string $charset
     * @param  string $encoding
     * @return Zend_Mail Provides fluent interface
     */
    public function setBodyText ($txt, $charset = null, $encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE)
    {
        if ($this->_devMode) {
            if (count($this->_tempRecipients) == 0) {
                // Throw error if the body is set before adding all recipients.
                // All recipients should be known in dev mode before body is set.
                throw new Zend_Mail_Exception('Error: Should set e-mail body only after adding ALL the recipients!');
            }
            $txt = "Orginal reciepients:\n" . implode("\n", $this->_tempRecipients) . "\n\nOriginal text:\n" . $txt;
            return parent::setBodyText($txt, $charset, $encoding);
        } else {
            return parent::setBodyText($txt, $charset, $encoding);
        }
    }
    /**
     * Sets the HTML body for the message
     *
     * @param  string    $html
     * @param  string    $charset
     * @param  string    $encoding
     * @return Zend_Mail Provides fluent interface
     */
    public function setBodyHtml ($html, $charset = null, $encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE)
    {
        if ($this->_devMode) {
            if (count($this->_tempRecipients) == 0) {
                // Throw error if the body is set before adding all recipients.
                // All recipients should be known in dev mode before body is set.
                throw new Zend_Mail_Exception('Error: Should set e-mail body only after adding ALL the recipients!');
            }
            $html = 'Orginal reciepients:<br/>' . implode('<br/>', $this->_tempRecipients) . '<br/><br/>Original text:<br/>' . $html;
            return parent::setBodyHtml($html, $charset, $encoding);
        } else {
            return parent::setBodyHtml($html, $charset, $encoding);
        }
    }
    /**
     * Sets the subject of the message
     *
     * @param  string    $subject
     * @return Zend_Mail Provides fluent interface
     */
    public function setSubject ($subject)
    {
        if ($this->_devMode) {
            $subject = 'TEST ' . $subject;
        }
        return parent::setSubject($subject);
    }
}