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