Advertisement
voodooKobra

MiniMailQueue v0.1

Sep 27th, 2012
231
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 9.34 KB | None | 0 0
  1. <?
  2. /* MMQ -- Mini_Mail_Queue
  3.  *
  4.  * Created by Scott Arciszewski, 24 September 2012 - 27 September 2012
  5.  *      kobrasrealm@gmail.com                   @voodooKobra on Twitter
  6.  */
  7. class MiniMailQueue {
  8.   const VALIDATION_REGEX = '/[^@]@[A-Za-z0-9\.\-\_]{2,}\.[A-Za-z0-9]{2,}/';
  9.     // Regular expression for email validation
  10.   private $config = array(
  11.     'mail_temp_folder' => '/tmp/mmq/',     // contains a 1 if locked
  12.     'mail_last_send_file' => '/tmp/mmq/lastSend',
  13.     'mail_lock_file' => '/tmp/mmq/mailLock',     // contains a 1 if locked
  14.     'mail_master_file' => '/tmp/mmq/master.db',     // contains a 1 if locked
  15.     'mail_send_amount' => 30,                    // # emails per batch
  16.     'bytes_per_file' => 20,                    // # pseudorandom bytes in a filename
  17.     'mail_send_timeout' => 30                    // Time between batches
  18.      
  19.   ); // Default configuration. Can be changed here or through setopt(array('option' => 'value'));
  20.   private $master; // Master SQLite DB
  21.   public $slave; // Individual SQLite DB
  22.  
  23.   public function __construct($options=array()) {
  24.     $this->setopt($options);
  25.     if(!file_exists($this->config['mail_master_file'])) {
  26.       $this->createMaster();
  27.     } else {
  28.       $this->master = new PDO('sqlite:'.$this->config['mail_master_file']);
  29.     }
  30.   }
  31.   private function createMaster() {
  32.     // Construct the master DB file if it doesn't exist
  33.     if(!file_exists($this->config['mail_master_file'])) {
  34.       $this->master = new PDO('sqlite:'.$this->config['mail_master_file']);
  35.       chmod($this->config['mail_master_file'], 0777);
  36.       $this->master->exec("CREATE TABLE slaves (filename TEXT, sending INTEGER, birth INTEGER, death INTEGER);");
  37.     }
  38.   }
  39.   public function setopt($options) {
  40.     // Pass an array of new values:
  41.     foreach($options as $i => $v) {
  42.       if(isset($this->config[$i])) $this->config[$i] = $v;
  43.     }
  44.   }
  45.   // $to = array(); of addresses
  46.   // $subject = string
  47.   // $message = string
  48.   // $headers = if array, parsed into string. Otherwise, string
  49.   public function enqueue($to, $subject, $message, $headers) {
  50.     // Create an SQLite database for this jobtask
  51.     // Put the address of this DB into the master db
  52.     $dbFile = $this->randomName(); // bytes
  53.     $dbPath = $this->config['mail_temp_folder'].$dbFile.'.db';
  54.     if(!is_array($to)) die("Must pass an array!");
  55.     if(is_array($headers)) $headers = $this->headerParse($headers);
  56.    
  57.     $this->slave = new PDO('sqlite:'.$dbPath) or die("ERROR: Could not create file {$dbPath}");
  58.       chmod($dbPath, 0777);
  59.       $this->master->exec("INSERT INTO slaves (filename, sending, birth, death) VALUES ('{$dbFile}', '1', '".time()."', '0');");
  60.     // Now, creat the table structures for the slave
  61.     $this->slave->exec("CREATE TABLE metadata (subject TEXT, message TEXT, headers TEXT);");
  62.     $this->slave->exec("CREATE TABLE recipients (address TEXT, received TEXT, time INTEGER);");
  63.     // Prepare statements, then execute. SQLi be damned
  64.     $st = $this->slave->prepare("INSERT INTO metadata (subject, message, headers) VALUES (?, ?, ?);");
  65.     $st->execute( array($subject, $message, $headers) );
  66.     $st = $this->slave->prepare("INSERT INTO recipients (address, received, time) VALUES (?, '0', '0');");
  67.     // Wonderful! Now to add data:
  68.     foreach($to as $t) {
  69.       $st->execute(array($t));
  70.     }
  71.     // Now we should be done.
  72.     return true;
  73.   }
  74.   protected function headerParse($array) {
  75.     // Parse into headers
  76.     $head = '';
  77.     foreach($array as $i => $v) {
  78.       $head .= "{$i}: {$v}\r\n";
  79.     }
  80.     return trim($head);
  81.   }
  82.   public function sendBatch() {
  83.     // Send ____ emails
  84.     // First step: Lock from double-changing
  85.     file_put_contents($this->config['mail_lock_file'], 1);
  86.     $sl = array();
  87.     foreach($this->master->query("SELECT ROWID as id, filename, sending, birth, death FROM slaves WHERE sending = '1'") as $slv) {
  88.       $sl[] = new MMQSlave($slv);
  89.     }
  90.     if(empty($sl)) return false;
  91.     $N = count($sl);
  92.     $L = $this->config['mail_send_amount'];
  93.     $LN = $L / ($N > 1 ? $N : 1);
  94.     foreach($sl as $s) {
  95.       $dbPath = $this->config['mail_temp_folder'].$s->filename.'.db';
  96.       $slave = new PDO('sqlite:'.$dbPath);
  97.       $batch = $slave->query("SELECT * FROM metadata");
  98.       $meta = $batch->fetch(PDO::FETCH_ASSOC); // Pull metadata
  99.       $j = 0;
  100.       foreach($slave->query("SELECT ROWID AS id, address, received, time FROM recipients WHERE received = '0' LIMIT 0, {$LN}") as $r) {
  101.         mail($r['address'], $meta['subject'], $meta['message'], $meta['headers']);
  102.           // Send the email
  103.         $slave->exec("UPDATE recipients SET received = '1', time = '".time()."' WHERE ROWID = '{$r['id']}'");
  104.           // Mark it as sent
  105.         $j++;
  106.       }
  107.       if($j < $LN) {
  108.         // We ran out of recipients!
  109.         $this->master->exec("UPDATE slaves SET sending = '0', death = '".time()."' WHERE id = '{$s->id}'");
  110.       }
  111.       $LN = ($LN - $j) + ($L / ($N > 1 ? $N : 1));
  112.       // If batch A only had 3 left to send and batch B has 27 left to send, still send them all this tick.
  113.     }
  114.     // Last step:
  115.     file_put_contents($this->config['mail_lock_file'], 0);
  116.     file_put_contents($this->config['mail_last_send_file'], time());
  117.   }
  118.   public function ready($autosend=false) {
  119.     // Is the file locked?
  120.       $locked = intval(file_get_contents($this->config['mail_lock_file']));
  121.       if($locked) return false;
  122.     // Has it been more than ____ seconds?
  123.       $lastsent = intval(file_get_contents($this->config['mail_last_send_file']));
  124.       if(time() - $lastsent < $this->config['mail_send_timeout']) return false;
  125.     // Yes, you can send something
  126.       if($autosend) return $this->sendBatch(); // Send a batch, if not told otherwise
  127.       return true;
  128.   }
  129.   public function randomName() {
  130.     // Generate a random filename for this database!
  131.     if(function_exists('bin2hex') && function_exists('openssl_random_pseudo_bytes')) {
  132.       // Strong randomization!
  133.        return $this->convBase(
  134.                 bin2hex(openssl_random_pseudo_bytes($this->config['bytes_per_file'])),
  135.                 '0123456789abcdef',
  136.                 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_' // Non-standard base64
  137.               );
  138.     } else {
  139.         if(is_readable('/dev/urandom')) {
  140.           // Screw you, PHP < 4.0! We'll do things my way.
  141.             $fp = fopen('/dev/urandom', 'rb');
  142.             list(, $hex) = unpack('H*', fread($fp, $this->config['bytes_per_file']));
  143.             fclose($fp);
  144.           return $this->convBase(
  145.                    bin2hex($hex),
  146.                    '0123456789abcdef',
  147.                    'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_' // Non-standard base64
  148.                  );
  149.         } else { // Weak randomization!
  150.           $acc = ''; // Accumulator
  151.           $target = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_';
  152.           while(strlen($acc) < $this->config['bytes_per_file']) {
  153.             $acc .= $target[mt_rand(0, 63)];
  154.           }
  155.           return $acc;
  156.             // Weaksauce, man
  157.         }
  158.     }
  159.   }
  160.   /*
  161.                                 ATTENTION!
  162.     /\        /\        /\        /\        /\        /\        /\        /\    
  163.    /##\      /##\      /##\      /##\      /##\      /##\      /##\      /##\  
  164.   /####\    /####\    /####\    /####\    /####\    /####\    /####\    /####\  
  165.  /######\  /######\  /######\  /######\  /######\  /######\  /######\  /######\
  166.  \######/  \######/  \######/  \######/  \######/  \######/  \######/  \######/  
  167.   \####/    \####/    \####/    \####/    \####/    \####/    \####/    \####/  
  168.    \##/      \##/      \##/      \##/      \##/      \##/      \##/      \##/  
  169.     \/        \/        \/        \/        \/        \/        \/        \/    
  170.  
  171.                 BELOW IS AUXULLIARY AND NOT WRITTEN BY SCOTT
  172.    */
  173.  
  174.   public function convBase($numberInput, $fromBaseInput, $toBaseInput) {
  175.   // http://php.net/manual/en/function.base-convert.php#106546
  176.     if ($fromBaseInput==$toBaseInput) return $numberInput;
  177.     $fromBase = str_split($fromBaseInput,1);
  178.     $toBase = str_split($toBaseInput,1);
  179.     $number = str_split($numberInput,1);
  180.     $fromLen=strlen($fromBaseInput);
  181.     $toLen=strlen($toBaseInput);
  182.     $numberLen=strlen($numberInput);
  183.     $retval='';
  184.     if ($toBaseInput == '0123456789')
  185.     {
  186.         $retval=0;
  187.         for ($i = 1;$i <= $numberLen; $i++)
  188.             $retval = bcadd($retval, bcmul(array_search($number[$i-1], $fromBase),bcpow($fromLen,$numberLen-$i)));
  189.         return $retval;
  190.     }
  191.     if ($fromBaseInput != '0123456789')
  192.         $base10=$this->convBase($numberInput, $fromBaseInput, '0123456789');
  193.     else
  194.         $base10 = $numberInput;
  195.     if ($base10<strlen($toBaseInput))
  196.         return $toBase[$base10];
  197.     while($base10 != '0')
  198.     {
  199.         $retval = $toBase[bcmod($base10,$toLen)].$retval;
  200.         $base10 = bcdiv($base10,$toLen,0);
  201.     }
  202.     return $retval;
  203.   }
  204. }
  205.  
  206. /*
  207.  *  MMQSlave :: Data structure for each recipient
  208.  */
  209. class MMQSlave {
  210.   public $id;
  211.   public $filename;
  212.   public $sending;
  213.   public $birth;
  214.   public $death;
  215.   public function __construct($sl) {
  216.     $this->id = $sl['id'];
  217.     $this->filename = $sl['filename'];
  218.     $this->sending = $sl['sending'];
  219.     $this->birth = $sl['birth'];
  220.     $this->death = $sl['death'];
  221.   }
  222. }
  223. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement