Archive

Posts Tagged ‘PHP’

A simple error handler for PHP

December 25th, 2009 No comments

What is the best way to handle your PHP application errors? There is no single good answer to that question. It all depends on how your application works, what environment the application runs in and also how maintenance is performed.

But one thing is the same for every application. You might already know that showing errors in your application to clients is not a very good practice. It’s a security risk because users can learn about the internals of your server and your application. Besides that, PHP error messages do not mean anything to a user of your application so it is pointless to show them what is wrong internally.
Alternatively you should use try and catch constructs to show friendly error messages to the users on the client side of your application if errors should be displayed to the user when they occur.

1
2
3
try {
} catch() {
}

You can use trigger_error() with the predefined error types E_USER_* to trigger your own application errors. For example to trigger PHP errors when your application encounters a MySQL error:

1
2
$query = 'SELECT YEAR(NOW())';
$result = mysql_query($query) or trigger_error(mysql_error().' - '.$query, E_USER_ERROR));

This way MySQL errors will be visible for you, but you do not give away anything about your database model to clients.
To see which predefined error constants there are available, see: http://www.php.net/manual/en/errorfunc.constants.php. A useful thing to note is that triggering notes or warnings does not stop the script from executing further. If you need to break the script and stop it from continuing, trigger an error.

So what can you do to get notified of PHP errors yourself, without having to keep up with the apache error log while not displaying any errors in the browser on the client side on your production application?
I’ve created a simple error handler that can replace or accompany the default PHP error handler.
This is a useful way of getting notified of errors, because you will get the errors in your mailbox directly when the error occurs. So you will know an error has occurred before a user can even send a bug report. This is not a solution for all situations, but it can sure be useful in some cases to get notified of errors you would otherwise miss, or would not be reported by users. Another useful feature is that you will receive the state of variables at the moment the error occurs. This can be immensely useful for debugging.

Here is the full error handler file (error_handler.php). You can put it into a PHP include directory so you can include it anywhere, or you can put it in one of the directories of your application.

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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
<?php
/*
 * Just a simple error handler.
 * It sends mails now, but can easily be modified to store the errors in a database instead of sending mails.
 * 
 * It optionally requires a database table for error checksums, this way you only get each error only once in your mailbox.
 * 
 * It's not very prety yet, but it is functional.
 * 
 * @author Oxidiser
 * @version 2.0
 * 
CREATE TABLE `error_checksums` (
  `checksum` varchar(32) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`checksum`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 */
// Mail where error e-mails should end up.
define('ERROR_REPORT_EMAIL', "someemailaddress@somedomain.com");
// Define if the original PHP error handler should also handle the error.
define('PHP_ERROR_HANDLER_ACTIVE', false);
// Should the error handler use checksums to prevent sending dulicate error messages?
define('USE_DATABASE', false);
// MySQL db options
define('MYSQL_HOST', "localhost");
define('MYSQL_USER', "");
define('MYSQL_PASSWORD', "");
define('MYSQL_DATABASE', "");
// Never display any errors to clients.
ini_set('display_errors', 0);
 
// Handle every error type. Good for test enviroment.
error_reporting(E_ALL);
// Handle every error type except notices and strict warnings. Good for the production enviroment.
//error_reporting(E_ALL & ~ E_NOTICE | E_STRICT);
 
// If error handler is not yet registered.
if(!is_callable('lookup_ErrorHandler')) {
    /**
     * Fetches an instance of the ErrorHandler object and calls the handleError() method;
     *
     * @param integer $errNo Contains the level of the error raised, as an integer.
     * @param string $errMsg Contains the error message, as a string.
     * @param string $fileName Contains the filename that the error was raised in, as a string.
     * @param integer $lineNum Contains the line number the error was raised at, as an integer.
     * @param array $vars An array that points to the active symbol table at the point the error occurred. In other words, errcontext will contain an array of every variable that existed in the scope the error was triggered in. User error handler must not modify error context.
     * @return void
     * 
     */
    function lookup_ErrorHandler ($errNo, $errMsg, $fileName, $lineNum, $vars)
    {
        // Run trough all error types.
        // To disable one of the error types, just comment out that case.
        // It might be useful to disable the notice error levels for sloppy programmers for instance.
        switch($errNo) {
            case E_ERROR:
            case E_WARNING:
            case E_PARSE:
            case E_USER_ERROR:
            case E_USER_WARNING:
            case E_STRICT:
            case E_COMPILE_ERROR:
            case E_COMPILE_WARNING:
            case E_CORE_ERROR:
            case E_CORE_WARNING:
            case E_RECOVERABLE_ERROR:
            case E_NOTICE:
            case E_USER_NOTICE:
                ErrorHandler::getInstance(ERROR_HANDLER_SUBJ_APPEND)->handleError($errNo, $errMsg, $fileName, $lineNum, $vars);
                break;
            default:
                // Error type should not be handled. Do nothing.
                break;
        }
        // Do or don't execute PHP internal error handler, return false if it should execute the PHP error handler, true if it should not.
        return !PHP_ERROR_HANDLER_ACTIVE;
    }
}
/**
 * This class replaces the default PHP error handler.
 * 
 * @param string $subjAppend This should contain the prefix for the e-mail subject. Useful for sorting e-mails by website when using the same error handler, when you for instance, place the error handler in an include directory. 
 * @version 1.0
 * @author Oxidiser
 * 
 */
class ErrorHandler
{
    /*
     * @var mixed (array with strings containing every error encountered during script execution)
     */
    public $errorsARR;
    /*
     * @var string A variable trace.
     */
    public $vars;
    /*
     * @var string (containing a string to prepend the error subject e-mail)
     */
    public $subjectAppend;
    /*
     * @var ErrorHandler
     */
    private static $instance;
    /*
     * @var MySQL link identifier
     */
    protected $_errorDb;
    /**
     * Create a new instance of the ErrorHandler object.
     * 
     * @param string $subjAppend The string that should be appended to the subject.
     * @return void
     */
    private function __construct($subjAppend)
    {
        $this->errormail = ERROR_REPORT_EMAIL;
        $this->subjectAppend = $subjAppend;
        $this->errortype = array(E_ERROR => 'Error',
                                 E_WARNING => 'Warning',
                                 E_PARSE => 'Parsing Error',
                                 E_NOTICE => 'Notice',
                                 E_CORE_ERROR => 'Core Error',
                                 E_CORE_WARNING => 'Core Warning',
                                 E_COMPILE_ERROR => 'Compile Error',
                                 E_COMPILE_WARNING => 'Compile Warning',
                                 E_USER_ERROR => 'User Error',
                                 E_USER_WARNING => 'User Warning',
                                 E_USER_NOTICE => 'User Notice',
                                 E_STRICT => 'Runtime Notice',
                                 E_RECOVERABLE_ERROR => 'Catchable Fatal Error'
                                );
        $this->errorsARR = array();
        if(USE_DATABASE) {
            // Database should be used, connect to MySQL instance.
            $this->_errorDb = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD);
            // Select the apropriate database.
            mysql_select_db(MYSQL_DATABASE, $this->_errorDb);
        }
    }
    /**
     * Clone the object (required for the singleton)
     * 
     * @return void
     */
    private function __clone ()
    {}
    /**
     * Returns the instance of the ErrorHandler object.
     *
     * @param string $subjAppend This should contain the prefix for the e-mail subject. Usefull for sorting e-mails by website.
     * @return ErrorHandler
     */
    public static function getInstance ($subjAppend)
    {
        if(!ErrorHandler::$instance instanceof self) {
            ErrorHandler::$instance = new self($subjAppend);
        }
        return ErrorHandler::$instance;
    }
    /**
     * This mehtod actually handles the errors recieved from the PHP core.
     *
     * @param integer $errNo Contains the level of the error raised, as an integer.
     * @param string $errMsg Contains the error message, as a string.
     * @param string $fileName Contains the filename that the error was raised in, as a string.
     * @param integer $lineNum Contains the line number the error was raised at, as an integer.
     * @param array $vars An array that points to the active symbol table at the point the error occurred. In other words, errcontext will contain an array of every variable that existed in the scope the error was triggered in. User error handler must not modify error context.
     * @return void
     * 
     */
    public function handleError($errNo, $errMsg, $fileName, $lineNum, $vars)
    {
        if(USE_DATABASE) {
            // Use database for checksums.
            // Check checksum in the hope of reducing server load by only mailing errors once.
            $errorChecksum = md5($errNo . $errMsg . $fileName . $lineNum);
            // Check if checksum exists in database.
            // @todo What is faster? Check with a select statement like it does now, or only do an insert query and check for an exception (mysql error, primairy key already exists)?
            $q = mysql_query("SELECT checksum FROM error_checksums WHERE checksum = '{$errorChecksum}'", $this->_errorDb) or trigger_error('Something went wrong with the error handler checksum check query: '.mysql_error(), E_USER_ERROR);
            if(mysql_num_rows($q) > 0) {
                // Error already mailed. Return without adding error to array in object and adding checksum to the database.
                return 0;
            }
            // Add checksum to database.
            mysql_query("INSERT INTO error_checksums (checksum) VALUES ('{$errorChecksum}')", $this->_errorDb) or trigger_error('Something went wrong with the error handler checksum insert query: '.mysql_error(), E_USER_ERROR);
        }
        // Get vars from thrown error.
        $this->vars = $vars;
        // Get the current date.
        $dt = date("Y-m-d H:i:s (T)");
        // Get the next empty key (auto increment).
        $nextKey = count($this->errorsARR);
        // get defined vars.
        $this->errorsARR[$nextKey]['date'] = $dt;
        $this->errorsARR[$nextKey]['errno'] = $errNo;
        $this->errorsARR[$nextKey]['errtype'] = $this->errortype[$errNo];
        $this->errorsARR[$nextKey]['errmessage'] = $errMsg;
        $this->errorsARR[$nextKey]['script'] = $fileName;
        $this->errorsARR[$nextKey]['url'] = 'http://'.$_SERVER['SERVER_NAME'].($_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : ($_REQUEST['SCRIPT_NAME'] ? $_REQUEST['SCRIPT_NAME'] : ($_ENV['SCRIPT_NAME'] ? $_ENV['SCRIPT_NAME'] : ($PHP_SELF ? $PHP_SELF : 'oopsnoscript')))) . ($_SERVER['QUERY_STRING'] ? '?' . $_SERVER['QUERY_STRING'] : '');
        $this->errorsARR[$nextKey]['lineno'] = $lineNum;
        // Get the state of the superglobals at the point of the error.
        $this->errorsARR[$nextKey]['session'] = $_SESSION;
        $this->errorsARR[$nextKey]['request'] = $_REQUEST;
        if(USE_DATABASE) {
            $this->errorsARR[$nextKey]['checksum'] = $errorChecksum;
        }
        if(isset($_SERVER['REMOTE_ADDR'])) {
            // Remote address is set, so store the client IP.
            $this->errorsARR[$nextKey]['user'] = $_SERVER['REMOTE_ADDR'];
        } else {
            // Unknown remote address.
            $this->errorsARR[$nextKey]['user'] = "Unknown";
        }
        // Extract the filename.
        $tmpArr = explode('/', $fileName);
        $errorFileName = array_pop($tmpArr);
        // Set the subject for this errors e-mail.
        $this->errorsARR[$nextKey]['subject'] = $this->errortype[$errNo] . ' in ' . $errorFileName. ' line #' . $lineNum;
    }
    /**
     * Destructor for the object. Sends (unique if checksums are used) errors once for each script execution at the end of execution.
     *
     * @return void
     */
    function __destruct ()
    {
        // Check if any errors were triggered during execution.
        if (count($this->errorsARR) > 0) {
            // Set headers for error mails.
            $headers = "From: \"somesite.nl Error reporter\" <errorhandler@somesite.nl>\n";
            $headers .= "Return-Path: <errorhandler@somesite.nl>\n";
            $headers .= "MIME-Version: 1.0\n";
            $headers .= "Content-Type: text/HTML; charset=ISO-8859-1\n";
            // Go through each error that was encountered.
            foreach($this->errorsARR as $err) {
                // Create content for error mail. 
                $content = "<table width=\"100%\" style=\"border:1px solid;\">";
                $content .= "<tr><td style=\"border:1px solid; background-color:#9FC2FF;\" colspan=\"2\"><strong>Message & Vitals</strong></td></tr>";
                $content .= "<tr><td style=\"border:1px solid;\"><strong>Error #</strong></td><td style=\"border:1px solid;\">{$err['errno']}</td></tr>";
                $content .= "<tr><td style=\"border:1px solid;\"><strong>Error type</strong></td><td style=\"border:1px solid;\">{$err['errtype']}</td></tr>";
                $content .= "<tr><td style=\"border:1px solid;\"><strong>Error message</strong></td><td style=\"border:1px solid;\">{$err['errmessage']}</td></tr>";
                $content .= "<tr><td style=\"border:1px solid;\"><strong>Date & time</strong></td><td style=\"border:1px solid;\">{$err['date']}</td></tr>";
                $content .= "<tr><td style=\"border:1px solid;\"><strong>URL</strong></td><td style=\"border:1px solid;\">{$err['url']}</td></tr>";
                $content .= "<tr><td style=\"border:1px solid;\"><strong>File</strong></td><td style=\"border:1px solid;\">{$err['script']}</td></tr>";
                $content .= "<tr><td style=\"border:1px solid;\"><strong>Line</strong></td><td style=\"border:1px solid;\">{$err['lineno']}</td></tr>";
                if(USE_DATABASE) {
                    $content .= "<tr><td style=\"border:1px solid;\"><strong>Error checksum</strong></td><td style=\"border:1px solid;\">{$err['checksum']}</td></tr>";
                }
                $content .= "<tr><td style=\"border:1px solid; background-color:#9FC2FF;\" colspan=\"2\"><strong>\$_SERVER</strong></td></tr>";
                // Format the server superglobal.
                foreach($_SERVER as $key=>$val) {
                    $content .= "<tr><td style=\"border:1px solid;\"><strong>{$key}</strong></td><td style=\"border:1px solid;\">{$val}</td></tr>";
                }
                $content .= "<tr><td style=\"border:1px solid; background-color:#9FC2FF;\" colspan=\"2\"><strong>\$_REQUEST</strong></td></tr>";;
                // Format the request superglobal.
                foreach($err['request'] as $key=>$val) {
                    $content .= "<tr><td style=\"border:1px solid;\"><strong>".print_r($key, true)."</strong></td><td style=\"border:1px solid;\"><pre>".print_r($val, true)."</pre></td></tr>";
                }
                $content .= "<tr><td style=\"border:1px solid; background-color:#9FC2FF;\" colspan=\"2\"><strong>\$_SESSION</strong></td></tr>";;
                // Format the session superglobal.
                foreach($err['session'] as $key=>$val) {
                    $content .= "<tr><td style=\"border:1px solid;\"><strong>".print_r($key, true)."</strong></td><td style=\"border:1px solid;\"><pre>".print_r($val, true)."</pre></td></tr>";
                }
                $content .= "<tr><td style=\"border:1px solid; background-color:#9FC2FF;\" colspan=\"2\"><strong>Stack trace</strong></td></tr>";
                $content .= "<tr><td style=\"border:1px solid;\" colspan=\"2\"><pre>".print_r($this->vars, true)."</pre></td></tr>";
                $content .= "</table>";
                // Dispatch the error mail.
                mail($this->errormail, $this->subjectAppend.' '.$err["subject"], $content, $headers);
            }
        }
        // Restore the deafult PHP error handler.
        restore_error_handler();
        // Restore the default PHP config value.
        ini_restore('display_errors');
    }
}
// Set the custom error handler to handle the errors.
set_error_handler('lookup_ErrorHandler');

Here is a small file with which you can test the error handler:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
define("ERROR_HANDLER_SUBJ_APPEND", 'WEBSITE IDENTIFIER');
// You can use this error handler by simply including it in your code. Preferably as high in the execution chain as possible.
// Include error handler from include path dir.
//include_once("error_handler.php");
// Include with absolute path.
include_once(dirname(__FILE__)."/error_handler.php");
 
session_start();
$_SESSION['justatest'] = array('anarraykey' => 'withanarrayvalue');
 
trigger_error('This is just a test.', E_USER_WARNING);
Categories: PHP Tags: , , ,

Using a custom view helper in Zend Framework to create breadcrumbs

December 15th, 2009 1 comment

I don’t think I am the only one who needs to put breadcrumbs on a page with zend framework. So I’ll share my helper with you. You can use this helper in you views to set the breadcrumbs.

You can use this helper like you use the HeadTitle helper, it is based on that.
So, in you layout, select which separator you want to use, and if you want to use automatic escaping or not (Can’t use this if you use urls). You can also set a prefix, postfix and indent if you want, just like with the HeadTitle helper. You can set, append and prepend items to the breadcrumb trail.

This is the code for the layout and also an example of prepending an item to the breadcrumb:

1
2
3
4
5
6
7
8
9
10
  <?php
    // Set the separator for the breadcrumbs.
    $this->breadCrumb()->setSeparator(' > ');
    // Disable the auto escaping of html.
    $this->breadCrumb()->setAutoEscape(false);
    // Set the first (default) element of the breadcrumbs.
    $this->breadCrumb("Home", "/", Zend_View_Helper_Placeholder_Container_Abstract::PREPEND);
    // Display the breadcrumb.
    echo $this->breadCrumb();
    // ...

This is an example of the code for a view script:

1
2
3
4
5
<?php
// Set breadcrumbs for this view.
$this->breadCrumb($this->placeholder('category_name'), $this->url(array('category' => $this->placeholder('category_url_name')), 'category_route', true));
$this->breadCrumb($this->placeholder('subject_name'));
// ...

The full helper:

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
<?php
/** Zend_View_Helper_Placeholder_Container_Standalone */
require_once 'Zend/View/Helper/Placeholder/Container/Standalone.php';
 
/**
 * Helper for setting and retrieving breadcrumbs for HTML.
 * Based on the HeadTitle helper.
 *
 * @version 1.0
 * @uses Zend_View_Helper_Placeholder_Container_Standalone
 */
class Zend_View_Helper_BreadCrumb extends Zend_View_Helper_Placeholder_Container_Standalone
{
    /**
     * Registry key for placeholder
     * @var string
     */
    protected $_regKey = 'Zend_View_Helper_BreadCrumb';
 
    /**
     * Whether or not auto-translation is enabled
     * @var boolean
     */
    protected $_translate = false;
 
    /**
     * Translation object
     *
     * @var Zend_Translate_Adapter
     */
    protected $_translator;
 
    /**
     * Container for the urls of the items.
     * 
     * @var Zend_View_Helper_Placeholder_Container
     */
    protected $_urls;
 
    /**
     * Retrieve placeholder for title element and optionally set a new item.
     *
     * @param  string $title
     * @param  string $url
     * @param  string $setType
     * @param  string $separator
     * @return Zend_View_Helper_BreadCrumb
     */
    public function breadCrumb($title = null, $url = null, $setType = Zend_View_Helper_Placeholder_Container_Abstract::APPEND)
    {
        // Initialize the url placeholder container if it is not created yet.
        if(!is_object($this->_urls)) $this->_urls = new Zend_View_Helper_Placeholder_Container();
        if ($title) {
            // Title must be given.
            if ($setType == Zend_View_Helper_Placeholder_Container_Abstract::SET) {
                // Reset the objects and set the first item.
                $this->set($title);
                $this->_urls->set($url);
            } elseif ($setType == Zend_View_Helper_Placeholder_Container_Abstract::PREPEND) {
                // Prepend an item to the breadcrumb.
                $this->prepend($title);
                $this->_urls->prepend($url);
            } else {
                // Append an item to the breadcrumb.
                $this->append($title);
                $this->_urls->append($url);
            }
        }
        // Return self for chaining and display of the breadcrumbs.
        return $this;
    }
 
    /**
     * Sets a translation Adapter for translation
     *
     * @param  Zend_Translate|Zend_Translate_Adapter $translate
     * @return Zend_View_Helper_BreadCrumb
     */
    public function setTranslator($translate)
    {
        if ($translate instanceof Zend_Translate_Adapter) {
            $this->_translator = $translate;
        } elseif ($translate instanceof Zend_Translate) {
            $this->_translator = $translate->getAdapter();
        } else {
            require_once 'Zend/View/Exception.php';
            throw new Zend_View_Exception("You must set an instance of Zend_Translate or Zend_Translate_Adapter");
        }
        return $this;
    }
 
    /*
     * Retrieve translation object
     *
     * If none is currently registered, attempts to pull it from the registry
     * using the key 'Zend_Translate'.
     *
     * @return Zend_Translate_Adapter|null
     */
    public function getTranslator()
    {
        if (null === $this->_translator) {
            require_once 'Zend/Registry.php';
            if (Zend_Registry::isRegistered('Zend_Translate')) {
                $this->setTranslator(Zend_Registry::get('Zend_Translate'));
            }
        }
        return $this->_translator;
    }
 
    /**
     * Enables translation
     *
     * @return Zend_View_Helper_BreadCrumb
     */
    public function enableTranslation()
    {
        $this->_translate = true;
        return $this;
    }
 
    /**
     * Disables translation
     *
     * @return Zend_View_Helper_BreadCrumb
     */
    public function disableTranslation()
    {
        $this->_translate = false;
        return $this;
    }
 
    /**
     * Turn helper into string to echo.
     *
     * @param  string|null $indent
     * @param  string|null $locale
     * @return string
     */
    public function toString($indent = null, $locale = null)
    {
        $indent = (null !== $indent) ? $this->getWhitespace($indent) : $this->getIndent();
 
        $items = array();
        if($this->_translate && $translator = $this->getTranslator()) {
            // Translator is set.
            for($i = 0; $i < $this->count(); $i++) {
                // Loop through all breadcrumb items.
                // Get item and its url.
                $item = $this->offsetGet($i);
                $url = $this->_urls->offsetGet($i);
                if(null === $url) {
                    // No url is set, add an url-less item.
                    $items[] = $translator->translate($item, $locale);
                } else {
                    // Url is set, create HTML.
                    $items[] = '<a href="'.$url.'">'.$translator->translate($item, $locale).'</a>';
                }
            }
        } else {
            // No translation required.
            for($i = 0; $i < $this->count(); $i++) {
                // Loop through all breadcrumb items.
                // Get item and its url.
                $item = $this->offsetGet($i);
                $url = $this->_urls->offsetGet($i);
                if(null === $url) {
                    // No url is set, add an url-less item.
                    $items[] = $item;
                } else {
                    // Url is set, create HTML.
                    $items[] = '<a href="'.$url.'">'.$item.'</a>';
                }
            }
        }
        // Fetch set separator.
        $separator = $this->getSeparator();
        $output = '';
        // Check if a prefix was set.
        if(($prefix = $this->getPrefix())) {
            // Add prefix to beginning of string.
            $output  .= $prefix;
        }
        // Implode all items and append them to the string.
        $output .= implode($separator, $items);
        // Check if a postfix was set.
        if(($postfix = $this->getPostfix())) {
            // Add a postfix to the end of the string.
            $output .= $postfix;
        }
        // Check if escaping is enabled (should be disabled when using urls).
        $output = ($this->_autoEscape) ? $this->_escape($output) : $output;
        // Return the final string with breadcrumbs.
        return $indent . $output;
    }
}

PHP Security – Disabling potentially harmful functions

September 3rd, 2009 No comments

PHP Security – Disabling potentially harmful functions

Most instances of hacked PHP web-servers I’ve seen have been exploited by some hole in an application or some other way to upload bad PHP code. These PHP scripts run malicious code through the PHP functions that execute commands directly on the server. This can easily be countered by disabling those functions.

Just use the disable_functions configuration option in your global php.ini file:

disable_functions = "apache_child_terminate, apache_setenv, define_syslog_variables, escapeshellarg, escapeshellcmd, exec, fputs, fwrite, highlight_file, passthru, php_uname, popen, posix_getpwuid, posix_kill, posix_mkfifo, posix_setpgid, posix_setsid, posix_setuid, posix_setuid, posix_uname, proc_close, proc_get_status, proc_nice, proc_open, proc_terminate, shell_exec, system, eval"

You cannot set the disable_functions through set_ini() (see: http://www.php.net/manual/en/ini.list.php). So the PHP configuration modifying functions can be left enabled without risk. The code above also blocks some other potentially harmful functions.

Categories: PHP Tags: , , ,

Creating and transfering pdb ebook files for use on the iphone/ipod touch eReader application

August 2nd, 2009 No comments

I really love the eReader application on my iPod touch. I think it is the best application that I’ve come across uptill now to read books. But you can’t just drag and drop e-books to your iPhone or iPod touch because apple locks iPhone applications to only their own storage area to store and retrieve data. This is one of the hassles besides converting e-books to the required pdb palm os file format. This is a short post on how to get your e-books from your computer to your eReader application on your iPhone or iPod touch.

The first step is to convert your e-books to the pdb format required by eReader. I use PorDiBle for OS X to convert plain text  (.txt), ms word (.doc), HMTL (.html, .htm) and Rich text (.rtf) formats to .pdb. You can get it here: http://www.loghound.com/pordible/. When installed, just drag & drop the .txt file onto the application icon and it will prompt you to enter the books name. After this the application will convert it to a .pdb file in the same directory and with the same filename as the original. If you want to delete the source file automatically after converting, just open PorBiBle and select preferences from the menu.
But you’ll often have e-books in different formats. For example, the two most important: PDF and microsoft’s lit.
These you’ll have to convert to plain text before you can drop them into PorDiBle. To do that for PDF, just open the file in adobe acrobat reader, and select File>save as text from the menu. To convert lit format to html download a OS X program from this guy: http://hansr.net called lit2html, you can download lit2html here (right click & save as): http://www.hansr.net/Lit2html.dmg. You can drag & drop a lit file to the lit2html application, and it will create a folder containing all components inside the lit file. To convert this e-book to pdb, just drag the .htm file with the title of your book (generally the one with the largest filesize) to PorDiBle.
I’m not aware of any similar applications for windows. If you run windows you’ll have to look for conversion utilities. If you find any good ones, please reply to this post.

Now that you have your .pdb file you are ready to get them to you eReader application. Because the application storage can not be accessed externally you will have to download the e-books from inside the application. To do this you will need to browse to a website, containing links to your books with ereader as protocol. Example: ereader://http://www.somewebsite.com/book.pdb or on a local LAN server: ereader://192.168.1.3/books/book.pdb. You’ll need a web server to do this with your own books. I will not go into the particulars of setting one up on your local machine if you do not have one available. Many, many tutorials exist already for that purpose.
When you have a web server available to you, make sure it supports PHP scripts. I’ve created a PHP script that will automatically create a web page containing eReader links to all files in the directory where the scripts reside. The only thing you will have to do is create a directory on your web server (named ebooks for example) containing this script in a plain text file with the filename “index.php”. You can download the script here (right click, save as): http://weblog.oxidiser.nl/wp-content/uploads/2009/08/index.phps. Just rename the file from index.phps to index.php. You’re all set to download e-books into eReader after you’ve uploaded this file to your directory. The only thing left is drop in your pdb e-book files into this same directory. Just browse to this folder in safari on your iPhone or iPod touch (if you choose the directory e-books in the root of your web server it will be http://yourserver/ebooks). You will get a list with filenames of all the e-books currently in the ebooks folder. When you tap one, it will automatically start the eReader application and download the e-book into it.

That’s it! Now you have your e-book on your device ready to be read!

Pinging your sitemap to google after every edit or addition through a CMS with zend framework

January 28th, 2009 1 comment

This helpful action helper can be called from all edit and add actions from your CMS controllers. It checks if enough time has elapsed after the last ping, using Zend_Cache. It logs ping errors to the log through the logger that is initialized in the initializer (ZF >1.6 project layout). You can replace it with any action you like. The actual generation of the XML sitemap is not in the scope of this post.

<?php
/**
 * This action helper pings google to index the changed urls through the sitemap.
 * 
 * @version 1.0
 * @uses Zend_Controller_Action_Helper_Abstract
 */
require_once 'Zend/Controller/Action/Helper/Abstract.php';
require_once 'Zend/Cache.php';
require_once 'Zend/Date.php';
require_once 'Zend/Http/Client.php';
class Zend_Controller_Action_Helper_GoogleSitemapPing extends Zend_Controller_Action_Helper_Abstract
{
    /**
     * Executes the ping to google.
     * 
     * @return boolean Was google pinged or not?
     */
    public function direct ()
    {
        // Only submit sitemap if this is the production instance of this application.
        $env = Zend_Controller_Front::getInstance()->getPlugin('Initializer')->getEnv();
        if($env == 'production') {
            // Get sitemap publish cache.
            $cacheFrontendOptions = array('automatic_serialization' => true);
            $cacheBackendOptions = array('cache_dir' => APPLICATION_PATH . '/../tmp/googleSitemapPing');
            $cache = Zend_Cache::factory('Core', 'File', $cacheFrontendOptions, $cacheBackendOptions);
            $shouldPing = false;
            if(!($data = $cache->load('lastPing'))) {
                // Cache miss. Should ping.
                $shouldPing = true;
            } else {
                // Cache hit, check if enough time has elapsed since last ping.
                // May only submit sitemap once per hour. Once an hour is a bit much, lets make it two hours.
                if(Zend_Date::getTimestamp()-$data > 7200) {
                    // More than a hour has passed. Do ping to google.
                    $shouldPing = true;
                }
            }
            if($shouldPing) {
                // Do http request to the google sitemap ping frontend.
                // Setup new http client.
                $client = new Zend_Http_Client();
                $client->setConfig(array('timeout' => 10, 'useragent' => 'ZF Google sitemap pinger V1.0', 'maxredirects' => 2, 'keepalive' => true));
                $client->setUri('http://www.google.com/webmasters/tools/ping?sitemap='.urlencode('http://www.yoursite.com/google-sitemap/index/gz/on'));
                $response = $client->request();
                if ($response->isSuccessful()) {
                    // Set new timestamp.
                    $cache->save(Zend_Date::getTimestamp());
                    return true;
                } else {
                    // Ping not successful, add notice to error log.
                    $logger = Zend_Controller_Front::getInstance()->getPlugin('Initializer')->getLogger();
                    $logger->log('Could not ping google sitemap ping service!', Zend_Log::ERR); 
                }
            }
        }
        return false;
    }
}

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.

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);
    }
}