1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4: */
3/**
4 * +----------------------------------------------------------------------+
5 * | PEAR :: Mail :: Queue                                                |
6 * +----------------------------------------------------------------------+
7 * | Copyright (c) 1997-2008 Radek Maciaszek, 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 *
39 * PHP Version 4 and 5
40 *
41 * @category Mail
42 * @package  Mail_Queue
43 * @author   Radek Maciaszek <chief@php.net>
44 * @author   Lorenzo Alberton <l.alberton@quipo.it>
45 * @license  http://www.opensource.org/licenses/bsd-license.php The BSD License
46 * @version  CVS: $Id: Queue.php 309130 2011-03-11 17:34:24Z till $
47 * @link     http://pear.php.net/package/Mail_Queue
48 */
49
50/**
51 * This is special constant define start offset for limit sql queries to
52 * get mails.
53 */
54define('MAILQUEUE_START', 0);
55
56/**
57 * You can specify how many mails will be loaded to
58 * queue else object use this constant for load all mails from db.
59 */
60define('MAILQUEUE_ALL', -1);
61
62/**
63 * When you put new mail to queue you could specify user id who send e-mail.
64 * Else you could use system id: MAILQUEUE_SYSTEM or user unknown id: MAILQUEUE_UNKNOWN
65 */
66define('MAILQUEUE_SYSTEM', -1);
67define('MAILQUEUE_UNKNOWN', -2);
68
69/**
70 * This constant tells Mail_Queue how many times should try
71 * to send mails again if was any errors before.
72 */
73define('MAILQUEUE_TRY', 25);
74
75/**
76 * MAILQUEUE_ERROR constants
77 */
78define('MAILQUEUE_ERROR',                    -1);
79define('MAILQUEUE_ERROR_NO_DRIVER',          -2);
80define('MAILQUEUE_ERROR_NO_CONTAINER',       -3);
81define('MAILQUEUE_ERROR_CANNOT_INITIALIZE',  -4);
82define('MAILQUEUE_ERROR_NO_OPTIONS',         -5);
83define('MAILQUEUE_ERROR_CANNOT_CONNECT',     -6);
84define('MAILQUEUE_ERROR_QUERY_FAILED',       -7);
85define('MAILQUEUE_ERROR_UNEXPECTED',         -8);
86define('MAILQUEUE_ERROR_CANNOT_SEND_MAIL',   -9);
87define('MAILQUEUE_ERROR_NO_RECIPIENT',      -10);
88define('MAILQUEUE_ERROR_UNKNOWN_CONTAINER', -11);
89
90/**
91 * PEAR
92 * @ignore
93 */
94require_once 'PEAR.php';
95
96/**
97 * Mail
98 * @ignore
99 */
100require_once 'Mail.php';
101
102/**
103 * Mail_mime
104 * @ignore
105 */
106require_once 'Mail/mime.php';
107
108/**
109 * Mail_Queue_Error
110 */
111require_once 'Mail/Queue/Error.php';
112
113
114/**
115 * Mail_Queue - base class for mail queue managment.
116 *
117 * @category Mail
118 * @package  Mail_Queue
119 * @author   Radek Maciaszek <chief@php.net>
120 * @author   Lorenzo Alberton <l.alberton@quipo.it>
121 * @license  http://www.opensource.org/licenses/bsd-license.php The BSD License
122 * @version  Release: @package_version@
123 * @link     http://pear.php.net/package/Mail_Queue
124 */
125class Mail_Queue extends PEAR
126{
127    // {{{ Class vars
128
129    /**
130     * Mail options: smtp, mail etc. see Mail::factory
131     *
132     * @var array
133     */
134    var $mail_options;
135
136    /**
137     * Mail_Queue_Container
138     *
139     * @var object
140     */
141    var $container;
142
143    /**
144     * Reference to Pear_Mail object
145     *
146     * @var object
147     */
148    var $send_mail;
149
150    /**
151     * Pear error mode (when raiseError is called)
152     * (see PEAR doc)
153     *
154     * @var int $_pearErrorMode
155     * @access private
156     */
157    var $pearErrorMode = PEAR_ERROR_RETURN;
158
159    /**
160     * To catch errors in construct
161     * @var array
162     * @see self::Mail_Queue()
163     * @see self::factory()
164     * @see self::hasErrors()
165     * @access private
166     */
167    var $_initErrors = array();
168
169    // }}}
170    // {{{ __construct
171
172    function __construct($container_options, $mail_options)
173    {
174        return $this->Mail_Queue($container_options, $mail_options);
175    }
176
177    // }}}
178    // {{{ Mail_Queue
179
180    /**
181     * Mail_Queue constructor
182     *
183     * @param  array $container_options  Mail_Queue container options
184     * @param  array $mail_options  How send mails.
185     *
186     * @return Mail_Queue
187     *
188     * @access public
189     * @deprecated
190     */
191    function Mail_Queue($container_options, $mail_options)
192    {
193        $this->PEAR();
194        if (isset($mail_options['pearErrorMode'])) {
195            $this->pearErrorMode = $mail_options['pearErrorMode'];
196            // ugly hack to propagate 'pearErrorMode'
197            $container_options['pearErrorMode'] = $mail_options['pearErrorMode'];
198        }
199
200        if (!is_array($mail_options) || !isset($mail_options['driver'])) {
201            array_push($this->_initErrors, new Mail_Queue_Error(MAILQUEUE_ERROR_NO_DRIVER,
202                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__));
203        }
204        $this->mail_options = $mail_options;
205
206        if (!is_array($container_options) || !isset($container_options['type'])) {
207            array_push($this->_initErrors, new Mail_Queue_Error(MAILQUEUE_ERROR_NO_CONTAINER,
208                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__));
209        }
210        $container_type      = strtolower($container_options['type']);
211        $container_class     = 'Mail_Queue_Container_' . $container_type;
212        $container_classfile = $container_type . '.php';
213
214        // Attempt to include a custom version of the named class, but don't treat
215        // a failure as fatal.  The caller may have already included their own
216        // version of the named class.
217        if (!class_exists($container_class)) {
218            include_once 'Mail/Queue/Container/' . $container_classfile;
219        }
220        if (!class_exists($container_class)) {
221            array_push($this->_initErrors, new Mail_Queue_Error(MAILQUEUE_ERROR_UNKNOWN_CONTAINER,
222                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__));
223        } else {
224
225            unset($container_options['type']);
226
227            $this->container = new $container_class($container_options);
228            if(PEAR::isError($this->container)) {
229                array_push($this->_initErrors, new Mail_Queue_Error(MAILQUEUE_ERROR_CANNOT_INITIALIZE,
230                    $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__));
231            }
232        }
233    }
234
235    /**
236     * Factory is used to initialize Mail_Queue, this is necessary to catch possible
237     * errors during the initialization.
238     *
239     * @param array $container_options Options for the container.
240     * @param array $mail_options Options for mail.
241     *
242     * @return mixed Mail_Queue|Mail_Queue_Error
243     * @see self::Mail_Queue()
244     * @since 1.2.3
245     */
246    function factory($container_options, $mail_options)
247    {
248        $obj = new Mail_Queue($container_options, $mail_options);
249        if ($obj->hasErrors()) {
250            /**
251             * @see self::getErrors()
252             */
253            return new Mail_Queue_Error(MAILQUEUE_ERROR,
254                $this->pearErrorMode, E_USER_ERROR, __FILE__, __LINE__);
255        }
256        return $obj;
257    }
258    // }}}
259    // {{{ _Mail_Queue()
260
261    /**
262     * Mail_Queue desctructor
263     *
264     * @return void
265     * @access public
266     */
267    function _Mail_Queue()
268    {
269        unset($this);
270    }
271
272    // }}}
273    // {{{ __destruct
274
275    function __destruct()
276    {
277        $this->_Mail_Queue();
278    }
279
280    // }}}
281    // {{{ factorySendMail()
282
283    /**
284     * Provides an interface for generating Mail:: objects of various
285     * types see Mail::factory()
286     *
287     * @return void
288     *
289     * @access public
290     */
291    function factorySendMail()
292    {
293        $options = $this->mail_options;
294        unset($options['driver']);
295
296        $this->send_mail =& Mail::factory($this->mail_options['driver'], $options);
297    }
298
299    /**
300     * Returns the number of emails currently in the queue.
301     *
302     * @return int
303     */
304    function getQueueCount()
305    {
306        if (!is_a($this->container, 'mail_queue_container')) {
307            array_push(
308                $this->_initErrors,
309                new Mail_Queue_Error(
310                    MAILQUEUE_ERROR_NO_CONTAINER,
311                    $this->pearErrorMode,
312                    E_USER_ERROR,
313                    __FILE__,
314                    __LINE__
315                )
316            );
317            return 0;
318        }
319        return $this->container->getQueueCount();
320    }
321
322    // }}}
323    // {{{ setBufferSize()
324
325    /**
326     * Keep memory usage under control. You can set the max number
327     * of mails that can be in the preload buffer at any given time.
328     * It won't limit the number of mails you can send, just the
329     * internal buffer size.
330     *
331     * @param integer $size  Optional - internal preload buffer size
332     */
333    function setBufferSize($size = 10)
334    {
335        $this->container->buffer_size = $size;
336    }
337
338
339    // }}}
340    // {{{ sendMailsInQueue()
341
342   /**
343     * Send mails fom queue.
344     *
345     * Mail_Queue::sendMailsInQueue()
346     *
347     * @param integer $limit     Optional - max limit mails send.
348     *                           This is the max number of emails send by
349     *                           this function.
350     * @param integer $offset    Optional - you could load mails from $offset (by id)
351     * @param integer $try       Optional - hoh many times mailqueu should try send
352     *                           each mail. If mail was sent succesful it will be
353     *                           deleted from Mail_Queue.
354     * @param mixed   $callback  Optional, a callback (string or array) to save the
355     *                           SMTP ID and the SMTP greeting.
356     *
357     * @return mixed  True on success else MAILQUEUE_ERROR object.
358     */
359    function sendMailsInQueue($limit = MAILQUEUE_ALL, $offset = MAILQUEUE_START,
360                              $try = MAILQUEUE_TRY, $callback = null)
361    {
362        if (!is_int($limit) || !is_int($offset) || !is_int($try)) {
363            return Mail_Queue::raiseError(
364                "sendMailsInQueue(): limit, offset and try must be integer.",
365                MAILQUEUE_ERROR_UNEXPECTED
366            );
367        }
368
369        if ($callback !== null) {
370            if (!is_array($callback) && !is_string($callback)) {
371                return Mail_Queue::raiseError(
372                    "sendMailsInQueue(): callback must be a string or an array.",
373                    MAILQUEUE_ERROR_UNEXPECTED
374                );
375            }
376        }
377
378        $this->container->setOption($limit, $offset, $try);
379        while (($mail = $this->get()) && !PEAR::isError($mail)) {
380            $this->container->countSend($mail);
381
382            $result = $this->sendMail($mail, true);
383            if (PEAR::isError($result)) {
384                //remove the problematic mail from the buffer, but don't delete
385                //it from the db: it might be a temporary issue.
386                $this->container->skip();
387                PEAR::raiseError(
388                    'Error in sending mail: '.$result->getMessage(),
389                    MAILQUEUE_ERROR_CANNOT_SEND_MAIL, PEAR_ERROR_TRIGGER,
390                    E_USER_NOTICE
391                );
392                continue;
393            }
394
395            //take care of callback first, as it may need to retrieve extra data
396            //from the mail_queue table.
397            if ($callback !== null) {
398                $queued_as = null;
399                $greeting  = null;
400                if (isset($this->queued_as)) {
401                    $queued_as = $this->queued_as;
402                }
403                if (isset($this->greeting)) {
404                    $greeting = $this->greeting;
405                }
406                call_user_func($callback,
407                    array('id' => $mail->getId(),
408                          'queued_as' => $queued_as,
409                          'greeting'  => $greeting));
410            }
411
412            // delete email from queue?
413            if ($mail->isDeleteAfterSend()) {
414                $status = $this->deleteMail($mail->getId());
415            }
416
417            unset($mail);
418
419            if (isset($this->mail_options['delay'])
420                && $this->mail_options['delay'] > 0) {
421                sleep($this->mail_options['delay']);
422            }
423        }
424
425        // most likely from breaking the loop
426        if (isset($mail) && PEAR::isError($mail)) {
427            return $mail;
428        }
429
430        if (!empty($this->mail_options['persist']) && is_object($this->send_mail)) {
431            $this->send_mail->disconnect();
432        }
433        return true;
434    }
435
436    // }}}
437    // {{{ sendMailById()
438
439    /**
440     * Send Mail by $id identifier. (bypass Mail_Queue)
441     *
442     * @param integer $id  Mail identifier
443     * @param  bool   $set_as_sent
444     * @return mixed  boolean: true on success else false, or PEAR_Error
445     *
446     * @access public
447     * @uses   self::sendMail()
448     */
449    function sendMailById($id, $set_as_sent=true)
450    {
451        $mail =& $this->container->getMailById($id);
452        if (PEAR::isError($mail)) {
453            return $mail;
454        }
455        return $this->sendMail($mail, $set_as_sent);
456    }
457
458    // }}}
459    // {{{ sendMail()
460
461    /**
462     * Send mail from {@link Mail_Queue_Body} object
463     *
464     * @param object  Mail_Queue_Body object
465     * @return mixed  True on success else pear error class
466     * @param  bool   $set_as_sent
467     *
468     * @access public
469     * @see    self::sendMailById()
470     */
471    function sendMail($mail, $set_as_sent=true)
472    {
473        if (!is_a($mail, 'Mail_Queue_Body')) {
474            if (is_a($mail, 'Mail_Queue_Error')) {
475                return $mail;
476            }
477            return Mail_Queue_Error(
478                "Unknown object/type: " . get_class($mail),
479                MAILQUEUE_ERROR_UNEXPECTED
480            );
481        }
482        $recipient = $mail->getRecipient();
483        if (empty($recipient)) {
484            return new Mail_Queue_Error('Recipient cannot be empty.',
485                MAILQUEUE_ERROR_NO_RECIPIENT);
486        }
487
488        $hdrs = $mail->getHeaders();
489        $body = $mail->getBody();
490
491        if (empty($this->send_mail)) {
492            $this->factorySendMail();
493        }
494        if (PEAR::isError($this->send_mail)) {
495            return $this->send_mail;
496        }
497        $sent = $this->send_mail->send($recipient, $hdrs, $body);
498        if (!PEAR::isError($sent) && $sent && $set_as_sent) {
499            $this->container->setAsSent($mail);
500        }
501        if (isset($this->send_mail->queued_as)) {
502            $this->queued_as = $this->send_mail->queued_as;
503        }
504        if (isset($this->send_mail->greeting)) {
505            $this->greeting = $this->send_mail->greeting;
506        }
507        return $sent;
508    }
509
510    // }}}
511    // {{{ get()
512
513    /**
514     * Get next mail from queue. The emails are preloaded
515     * in a buffer for better performances.
516     *
517     * @return    object Mail_Queue_Container or error object
518     * @throw     MAILQUEUE_ERROR
519     * @access    public
520     */
521    function get()
522    {
523        return $this->container->get();
524    }
525
526    // }}}
527    // {{{ put()
528
529    /**
530     * Put new mail in queue.
531     *
532     * @see Mail_Queue_Container::put()
533     *
534     * @param string  $time_to_send  When mail have to be send
535     * @param integer $id_user  Sender id
536     * @param string  $ip    Sender ip
537     * @param string  $from  Sender e-mail
538     * @param string|array  $to    Reciepient(s) e-mail
539     * @param string  $hdrs  Mail headers (in RFC)
540     * @param string  $body  Mail body (in RFC)
541     * @return mixed  ID of the record where this mail has been put
542     *                or Mail_Queue_Error on error
543     *
544     * @access public
545     */
546    function put($from, $to, $hdrs, $body, $sec_to_send=0, $delete_after_send=true, $id_user=MAILQUEUE_SYSTEM)
547    {
548        $ip = getenv('REMOTE_ADDR');
549
550        $time_to_send = date("Y-m-d H:i:s", time() + $sec_to_send);
551
552        return $this->container->put(
553            $time_to_send,
554            $id_user,
555            $ip,
556            $from,
557            serialize($to),
558            serialize($hdrs),
559            serialize($body),
560            $delete_after_send
561        );
562    }
563
564    // }}}
565    // {{{ deleteMail()
566
567    /**
568     * Delete mail from queue database
569     *
570     * @param integer $id  Maila identifier
571     * @return boolean
572     *
573     * @access private
574     */
575    function deleteMail($id)
576    {
577        return $this->container->deleteMail($id);
578    }
579
580    // }}}
581    // {{{ isError()
582
583    /**
584     * Tell whether a result code from a Mail_Queue method is an error
585     *
586     * @param   int       $value  result code
587     * @return  boolean   whether $value is an MAILQUEUE_ERROR
588     * @access public
589     */
590    static function isError($value)
591    {
592        return (is_object($value) && is_a($value, 'pear_error'));
593    }
594
595    // }}}
596    // {{{ errorMessage()
597
598    /**
599     * Return a textual error message for a MDB error code
600     *
601     * @param   int     $value error code
602     * @return  string  error message, or false if the error code was
603     *                  not recognized
604     * @access public
605     */
606    function errorMessage($value)
607    {
608        static $errorMessages;
609        if (!isset($errorMessages)) {
610            $errorMessages = array(
611                MAILQUEUE_ERROR                    => 'unknown error',
612                MAILQUEUE_ERROR_NO_DRIVER          => 'No mail driver specified',
613                MAILQUEUE_ERROR_NO_CONTAINER       => 'No container specified',
614                MAILQUEUE_ERROR_CANNOT_INITIALIZE  => 'Cannot initialize container',
615                MAILQUEUE_ERROR_NO_OPTIONS         => 'No container options specified',
616                MAILQUEUE_ERROR_CANNOT_CONNECT     => 'Cannot connect to database',
617                MAILQUEUE_ERROR_QUERY_FAILED       => 'db query failed',
618                MAILQUEUE_ERROR_UNEXPECTED         => 'Unexpected class',
619                MAILQUEUE_ERROR_CANNOT_SEND_MAIL   => 'Cannot send email',
620            );
621        }
622
623        if (Mail_Queue::isError($value)) {
624            $value = $value->getCode();
625        }
626
627        return isset($errorMessages[$value]) ?
628           $errorMessages[$value] : $errorMessages[MAILQUEUE_ERROR];
629    }
630
631    // }}}
632
633    /**
634     * hasErrors() returns true/false, if self::$_initErrors are populated.
635     *
636     * @return boolean
637     * @see self::Mail_Queue
638     * @see self::$_initErrors
639     * @see self::getErrors()
640     * @since 1.2.3
641     */
642    function hasErrors()
643    {
644        if (count($this->_initErrors) > 0) {
645            return true;
646        }
647        return false;
648    }
649
650    /**
651     * getErrors() returns the errors.
652     *
653     * @return array
654     * @see self::Mail_Queue
655     * @see self::$_initErrors
656     * @see self::hasErrors()
657     * @since 1.2.3
658     */
659    function getErrors()
660    {
661        return $this->_initErrors;
662    }
663
664/*
665    function raiseError($msg, $code = null, $file = null, $line = null, $mode = null)
666    {
667        if ($file !== null) {
668            $err = PEAR::raiseError(sprintf("%s [%s on line %d].", $msg, $file, $line), $code, $mode);
669        } else {
670            $err = PEAR::raiseError(sprintf("%s", $msg), $code, $mode);
671        }
672� � � � return $err;
673    }
674*/
675}
676?>
677