1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4: */
3/**
4 * +----------------------------------------------------------------------+
5 * | PEAR :: Mail :: Queue :: MDB2 Container                              |
6 * +----------------------------------------------------------------------+
7 * | Copyright (c) 1997-2008 Lorenzo Alberton                             |
8 * +----------------------------------------------------------------------+
9 * | All rights reserved.                                                 |
10 * |                                                                      |
11 * | Redistribution and use in source and binary forms, with or without   |
12 * | modification, are permitted provided that the following conditions   |
13 * | are met:                                                             |
14 * |                                                                      |
15 * | * Redistributions of source code must retain the above copyright     |
16 * |   notice, this list of conditions and the following disclaimer.      |
17 * | * Redistributions in binary form must reproduce the above copyright  |
18 * |   notice, this list of conditions and the following disclaimer in    |
19 * |   the documentation and/or other materials provided with the         |
20 * |   distribution.                                                      |
21 * | * The names of its contributors may be used to endorse or promote    |
22 * |   products derived from this software without specific prior written |
23 * |   permission.                                                        |
24 * |                                                                      |
25 * | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
26 * | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
27 * | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
28 * | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE       |
29 * | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,  |
30 * | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
31 * | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;     |
32 * | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER     |
33 * | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT   |
34 * | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN    |
35 * | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE      |
36 * | POSSIBILITY OF SUCH DAMAGE.                                          |
37 * +----------------------------------------------------------------------+
38 * | Author: Lorenzo Alberton <l.alberton at quipo.it>                    |
39 * +----------------------------------------------------------------------+
40 */
41
42/**
43 * Storage driver for fetching mail queue data from a PEAR::MDB2 database
44 *
45 * This storage driver can use all databases which are supported
46 * by the PEAR MDB2 abstraction layer.
47 *
48 * PHP Version 4 and 5
49 *
50 * @category Mail
51 * @package  Mail_Queue
52 * @author   Lorenzo Alberton <l dot alberton at quipo dot it>
53 * @version  CVS: $Id: mdb2.php 303870 2010-09-29 16:25:34Z till $
54 * @license  http://www.opensource.org/licenses/bsd-license.php The BSD License
55 * @link     http://pear.php.net/package/Mail_Queue
56 */
57require_once 'MDB2.php';
58require_once 'Mail/Queue/Container.php';
59
60/**
61 * Mail_Queue_Container_mdb2
62 *
63 * @category Mail
64 * @package  Mail_Queue
65 * @author   Lorenzo Alberton <l dot alberton at quipo dot it>
66 * @version  Release: @package_version@
67 * @license  http://www.opensource.org/licenses/bsd-license.php The BSD License
68 * @link     http://pear.php.net/package/Mail_Queue
69 */
70class Mail_Queue_Container_mdb2 extends Mail_Queue_Container
71{
72    // {{{ class vars
73
74    /**
75     * Reference to the current database connection.
76     * @var object PEAR::MDB2 instance
77     */
78    var $db = null;
79
80    var $errorMsg = 'MDB2::query() failed: "%s", %s';
81
82    /**
83     * Table for sql database
84     * @var  string
85     */
86    var $mail_table = 'mail_queue';
87
88    /**
89     * @var string  the name of the sequence for this table
90     */
91    var $sequence = null;
92
93    // }}}
94    // {{{ __construct()
95
96    function __construct($options)
97    {
98        return $this->Mail_Queue_Container_mdb2($options);
99    }
100
101    // }}}
102    // {{{ Mail_Queue_Container_mdb2()
103
104    /**
105     * Constructor
106     *
107     * Mail_Queue_Container_mdb2()
108     *
109     * @param mixed $options    An associative array of connection option.
110     *
111     * @access public
112     */
113    function Mail_Queue_Container_mdb2($options)
114    {
115        if (!is_array($options) || !isset($options['dsn'])) {
116            return new Mail_Queue_Error(MAILQUEUE_ERROR_NO_OPTIONS,
117                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
118                'No dns specified!');
119        }
120        if (isset($options['mail_table'])) {
121            $this->mail_table = $options['mail_table'];
122        }
123        $this->sequence = (isset($options['sequence']) ? $options['sequence'] : $this->mail_table);
124
125        if (!empty($options['pearErrorMode'])) {
126            $this->pearErrorMode = $options['pearErrorMode'];
127        }
128        $dsn = array_key_exists('dsn', $options) ? $options['dsn'] : $options;
129        $res = $this->_connect($dsn);
130        if (PEAR::isError($res)) {
131            return $res;
132        }
133        $this->setOption();
134    }
135
136    // }}}
137    // {{{ _connect()
138
139    /**
140     * Connect to database by using the given DSN string
141     *
142     * @param mixed &$db DSN string | array | MDB2 object
143     *
144     * @return boolean|PEAR_Error on error
145     * @access private
146     */
147    function _connect(&$db)
148    {
149        if (is_object($db) && is_a($db, 'MDB2_Driver_Common')) {
150            $this->db = &$db;
151        } elseif (is_string($db) || is_array($db)) {
152            include_once 'MDB2.php';
153            $this->db =& MDB2::connect($db);
154        } elseif (is_object($db) && MDB2::isError($db)) {
155            return new Mail_Queue_Error(MAILQUEUE_ERROR_CANNOT_CONNECT,
156                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
157                'MDB2::connect failed: '. $this->_getErrorMessage($this->db));
158        } else {
159            return new Mail_Queue_Error(MAILQUEUE_ERROR_CANNOT_CONNECT,
160                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
161                'The given dsn was not valid in file '. __FILE__ . ' at line ' . __LINE__);
162        }
163        if (PEAR::isError($this->db)) {
164            return $this->db;
165        }
166        return true;
167    }
168
169    // }}}
170    // {{{ _checkConnection()
171
172    /**
173     * Check if there's a valid db connection
174     *
175     * @return boolean|PEAR_Error on error
176     */
177    function _checkConnection() {
178        if (!is_object($this->db) || !is_a($this->db, 'MDB2_Driver_Common')) {
179            $msg = 'MDB2::connect failed';
180            if (PEAR::isError($this->db)) {
181                $msg .= $this->_getErrorMessage($this->db);
182            }
183            return new Mail_Queue_Error(MAILQUEUE_ERROR_CANNOT_CONNECT,
184                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__, $msg);
185        }
186        return true;
187    }
188
189    /**
190     * Create a more useful error message from DB related errors.
191     *
192     * @access private
193     *
194     * @param PEAR_Error $errorObj A PEAR_Error object.
195     *
196     * @return string
197     */
198    function _getErrorMessage($errorObj)
199    {
200        if (!Pear::isError($errorObj)) {
201            return '';
202        }
203        $msg   = ': ' . $errorObj->getMessage();
204        $debug = $errorObj->getDebugInfo();
205
206        if (!empty($debug)) {
207            $msg .= ", DEBUG: {$debug}";
208        }
209        return $msg;
210    }
211
212    // }}}
213    // {{{ _preload()
214
215    /**
216     * Preload mail to queue.
217     *
218     * @return mixed  True on success else Mail_Queue_Error object.
219     * @access private
220     */
221    function _preload()
222    {
223        $res = $this->_checkConnection();
224        if (PEAR::isError($res)) {
225            return $res;
226        }
227        $query = 'SELECT * FROM ' . $this->mail_table
228                .' WHERE sent_time IS NULL AND try_sent < '. $this->try
229                .' AND time_to_send <= '.$this->db->quote(date('Y-m-d H:i:s'), 'timestamp')
230                .' ORDER BY time_to_send';
231        $this->db->setLimit($this->limit, $this->offset);
232        $res = $this->db->query($query);
233        if (PEAR::isError($res)) {
234            return new Mail_Queue_Error(MAILQUEUE_ERROR_QUERY_FAILED,
235                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
236                sprintf($this->errorMsg, $query, $this->_getErrorMessage($res)));
237        }
238
239        $this->_last_item = 0;
240        $this->queue_data = array(); //reset buffer
241        while ($row = $res->fetchRow(MDB2_FETCHMODE_ASSOC)) {
242            //var_dump($row['headers']);
243            if (!is_array($row)) {
244                return new Mail_Queue_Error(MAILQUEUE_ERROR_QUERY_FAILED,
245                    $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
246                    sprintf($this->errorMsg, $query, $this->_getErrorMessage($res)));
247            }
248
249            $delete_after_send = (bool) $row['delete_after_send'];
250
251            $this->queue_data[$this->_last_item] = new Mail_Queue_Body(
252                $row['id'],
253                $row['create_time'],
254                $row['time_to_send'],
255                $row['sent_time'],
256                $row['id_user'],
257                $row['ip'],
258                $row['sender'],
259                $this->_isSerialized($row['recipient']) ? unserialize($row['recipient']) : $row['recipient'],
260                unserialize($row['headers']),
261                unserialize($row['body']),
262                $delete_after_send,
263                $row['try_sent']
264            );
265            $this->_last_item++;
266        }
267
268        return true;
269    }
270
271    // }}}
272    // {{{ put()
273
274    /**
275     * Put new mail in queue and save in database.
276     *
277     * Mail_Queue_Container::put()
278     *
279     * @param string $time_to_send  When mail have to be send
280     * @param integer $id_user  Sender id
281     * @param string $ip  Sender ip
282     * @param string $from  Sender e-mail
283     * @param string $to  Reciepient e-mail
284     * @param string $hdrs  Mail headers (in RFC)
285     * @param string $body  Mail body (in RFC)
286     * @param bool $delete_after_send  Delete or not mail from db after send
287     *
288     * @return mixed  ID of the record where this mail has been put
289     *                or Mail_Queue_Error on error
290     * @access public
291     **/
292    function put($time_to_send, $id_user, $ip, $sender,
293                $recipient, $headers, $body, $delete_after_send=true)
294    {
295        $res = $this->_checkConnection();
296        if (PEAR::isError($res)) {
297            return $res;
298        }
299        $id = $this->db->nextID($this->sequence);
300        if (empty($id) || PEAR::isError($id)) {
301            return new Mail_Queue_Error(MAILQUEUE_ERROR,
302                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
303                'Cannot create id in: '.$this->sequence);
304        }
305        $query = 'INSERT INTO '. $this->mail_table
306                .' (id, create_time, time_to_send, id_user, ip'
307                .', sender, recipient, headers, body, delete_after_send) VALUES ('
308                .       $this->db->quote($id, 'integer')
309                .', ' . $this->db->quote(date('Y-m-d H:i:s'), 'timestamp')
310                .', ' . $this->db->quote($time_to_send, 'timestamp')
311                .', ' . $this->db->quote($id_user, 'integer')
312                .', ' . $this->db->quote($ip, 'text')
313                .', ' . $this->db->quote($sender, 'text')
314                .', ' . $this->db->quote($recipient, 'text')
315                .', ' . $this->db->quote($headers, 'text')   //clob
316                .', ' . $this->db->quote($body, 'text')      //clob
317                .', ' . ($delete_after_send ? 1 : 0)
318                .')';
319        $res = $this->db->query($query);
320        if (PEAR::isError($res)) {
321            return new Mail_Queue_Error(MAILQUEUE_ERROR_QUERY_FAILED,
322                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
323                sprintf($this->errorMsg, $query, $this->_getErrorMessage($res)));
324        }
325        return $id;
326    }
327
328    // }}}
329    // {{{ countSend()
330
331    /**
332     * Check how many times mail was sent.
333     *
334     * @param object   Mail_Queue_Body
335     * @return mixed  Integer or Mail_Queue_Error class if error.
336     * @access public
337     */
338    function countSend($mail)
339    {
340        $res = $this->_checkConnection();
341        if (PEAR::isError($res)) {
342            return $res;
343        }
344        if (!is_object($mail) || !is_a($mail, 'mail_queue_body')) {
345            return new Mail_Queue_Error(MAILQUEUE_ERROR_UNEXPECTED, __FILE__, __LINE__);
346        }
347        $count = $mail->_try();
348        $query = 'UPDATE ' . $this->mail_table
349                .' SET try_sent = ' . $this->db->quote($count, 'integer')
350                .' WHERE id = '     . $this->db->quote($mail->getId(), 'integer');
351        $res = $this->db->query($query);
352        if (PEAR::isError($res)) {
353            return new Mail_Queue_Error(MAILQUEUE_ERROR_QUERY_FAILED,
354                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
355                sprintf($this->errorMsg, $query, $this->_getErrorMessage($res)));
356        }
357        return $count;
358    }
359
360    // }}}
361    // {{{ setAsSent()
362
363    /**
364     * Set mail as already sent.
365     *
366     * @param object Mail_Queue_Body object
367     * @return bool
368     * @access public
369     */
370    function setAsSent($mail)
371    {
372        $res = $this->_checkConnection();
373        if (PEAR::isError($res)) {
374            return $res;
375        }
376        if (!is_object($mail) || !is_a($mail, 'mail_queue_body')) {
377            return new Mail_Queue_Error(MAILQUEUE_ERROR_UNEXPECTED,
378                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
379               'Expected: Mail_Queue_Body class');
380        }
381        $query = 'UPDATE ' . $this->mail_table
382                .' SET sent_time = '.$this->db->quote(date('Y-m-d H:i:s'), 'timestamp')
383                .' WHERE id = '. $this->db->quote($mail->getId(), 'integer');
384
385        $res = $this->db->query($query);
386        if (PEAR::isError($res)) {
387            return new Mail_Queue_Error(MAILQUEUE_ERROR_QUERY_FAILED,
388                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
389                sprintf($this->errorMsg, $query, $this->_getErrorMessage($res)));
390        }
391        return true;
392    }
393
394    // }}}
395    // {{{ getMailById()
396
397    /**
398     * Return mail by id $id (bypass mail_queue)
399     *
400     * @param integer $id  Mail ID
401     * @return mixed  Mail object or false on error.
402     * @access public
403     */
404    function getMailById($id)
405    {
406        $res = $this->_checkConnection();
407        if (PEAR::isError($res)) {
408            return $res;
409        }
410        $query = 'SELECT * FROM ' . $this->mail_table
411                .' WHERE id = '   . (int)$id;
412        $row = $this->db->queryRow($query, null, MDB2_FETCHMODE_ASSOC);
413        if (PEAR::isError($row)) {
414            return new Mail_Queue_Error(MAILQUEUE_ERROR_QUERY_FAILED,
415                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
416                sprintf($this->errorMsg, $query, $this->_getErrorMessage($row)));
417        }
418        if (!is_array($row)) {
419            return new Mail_Queue_Error(MAILQUEUE_ERROR_QUERY_FAILED,
420                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
421                sprintf($this->errorMsg, $query, 'no such message'));
422        }
423
424        $delete_after_send = (bool) $row['delete_after_send'];
425
426        return new Mail_Queue_Body(
427            $row['id'],
428            $row['create_time'],
429            $row['time_to_send'],
430            $row['sent_time'],
431            $row['id_user'],
432            $row['ip'],
433            $row['sender'],
434            $this->_isSerialized($row['recipient']) ? unserialize($row['recipient']) : $row['recipient'],
435            unserialize($row['headers']),
436            unserialize($row['body']),
437            $delete_after_send,
438            $row['try_sent']
439        );
440    }
441
442    /**
443     * Return the number of emails currently in the queue.
444     *
445     * @return mixed An int, or Mail_Queue_Error on failure.
446     */
447    function getQueueCount()
448    {
449        $res = $this->_checkConnection();
450        if (PEAR::isError($res)) {
451            return $res;
452        }
453        $query = 'SELECT count(*) FROM ' . $this->mail_table;
454        $count = $this->db->queryOne($query);
455        if (PEAR::isError($count)) {
456            return new Mail_Queue_Error(MAILQUEUE_ERROR_QUERY_FAILED,
457                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
458                sprintf($this->errorMsg, $query, $this->_getErrorMessage($count)));
459        }
460        return (int) $count;
461    }
462
463    // }}}
464    // {{{ deleteMail()
465
466    /**
467     * Remove from queue mail with $id identifier.
468     *
469     * @param integer $id  Mail ID
470     * @return bool  True on success else Mail_Queue_Error class
471     *
472     * @access public
473     */
474    function deleteMail($id)
475    {
476        $res = $this->_checkConnection();
477        if (PEAR::isError($res)) {
478            return $res;
479        }
480        $query = 'DELETE FROM ' . $this->mail_table
481                .' WHERE id = ' . $this->db->quote($id, 'text');
482        $res = $this->db->query($query);
483
484        if (PEAR::isError($res)) {
485            return new Mail_Queue_Error(MAILQUEUE_ERROR_QUERY_FAILED,
486                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__,
487                sprintf($this->errorMsg, $query, $this->_getErrorMessage($res)));
488        }
489        return true;
490    }
491
492    // }}}
493}
494?>
495