Location: PHPKode > projects > phpLedMailer > common.inc.php
<?
/*
*
* $Id: common.inc.php,v 1.72 2005/09/07 20:35:12 ledjon Exp $
*
* phpLedMailer - v1.x
*
* by Jon Coulter
* http://www.ledscripts.com/
*
* Changes:
* 	- None yet
*
*/

// set error reporting level
//error_reporting(E_ALL & ~E_NOTICE);
error_reporting(error_reporting() & ~E_NOTICE);

// auto-start session(s)
if(! headers_sent( ) )
{
	session_start( );
}

// install file still there?
if(file_exists( dirname(__FILE__) . '/install.php' )
	&& !defined('PHPLEDMAILER_INSTALLER'))
{
	// make sure that install.php doesn't exist anymore
	die("Please delete install.php before you continue " .
		"(or run install.php, if you have not yet configured phpLedMailer.)");
}

// config file
require_once(dirname(__FILE__) . '/config.inc.php');

// ADODB database abstraction layer
define('ADODB_ASSOC_CASE', 0); // lower case
require_once(dirname(__FILE__) . '/lib/adodb/adodb.inc.php');

require_once(dirname(__FILE__) . '/lib/html/class.LedHTML.php');
require_once(dirname(__FILE__) . '/lib/template/class.LedTemplate.php');

// moved to $db->SetFetchMode(ADODB_FETCH_ASSOC);
// $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;

class phpLedMailer
{
	var $version = '1.8';
	var $db = null;
	var $html = null;
	var $template = null;
	var $errstr = null;
	var $mailer = null;
	
	function phpLedMailer( $config )
	{
		$this->config = $config;

		$this->template = new LedTemplate( );
		
		// database specific fixes
		if($config['db']['type'] == "mssql")
		{
			//$this->db->maxParameterLen = pow(2, 31) - 1;
			
			// odbc mssql (native mssql is limited to 6.5 functionality)
			$this->db = NewADOConnection('odbc_mssql') or die("Unable to create object: " . __LINE__);
			$dsn = sprintf("Driver={SQL Server};Server=%s;Database=%s;",
					$config['db']['host'],
					$config['db']['db']
				);
			$this->db->Connect($dsn, $config['db']['user'], $config['db']['pass'])
				or die("<b>Unable to connect to database!</b> Make sure you have the database configuration correct in config.inc.php");
		}
		else
		{
			// oracle stuff
			if($config['db']['type'] == 'oracle'
				|| $config['db']['type'] == 'oci8')
			{
				$config['db']['type'] = 'oci8po'; // portable version
			}
			
			// sqlite
			if($config['db']['type'] == 'sqlite')
			{
				$config['db']['type'] = 'sqlitepo'; // portable version
				$config['db']['host'] = $config['db']['db'];
			}
			
			// form a dsn for adodb
			$dsn = sprintf("%s://%s:%s@%s/%s",
					$config['db']['type'],
					$config['db']['user'],
					$config['db']['pass'],
					$config['db']['host'],
					$config['db']['db']
				);
			
			// echo $dsn;
			
			$this->db = NewADOConnection($dsn)
				or die("<b>Unable to connect to database!</b> Make sure you have the database configuration correct in config.inc.php");

			// remove these stupid quotes
			// this has to be rigged per-database
			if($this->db->dataProvider == 'mysql'
				|| $this->db->dataProvider == 'postgres'
				|| $this->db->databaseType == 'sqlitepo')
			{
				$this->db->fmtTimeStamp = str_replace("'", "", $this->db->fmtTimeStamp);			
			}
		}
		
		$this->db->SetFetchMode(ADODB_FETCH_ASSOC);

		// load config from the database
		$this->_LoadConfig( );
		
		// html settings
		$this->html = new LedHTML(
				array(
					'width' 		=> '700',
					'size'			=> 1,
					'border'		=> 1,
					'cellspacing'	=> 0,
					'cellpadding'	=> 0,
					'bordercolor'	=> "#669999",
					'align'			=> 'center',
					'input_class'	=> 'input',
				)
		);
		
		// I don't really like this -- it effectivly
		// doubles the amount of memory the incoming params
		// take at any point in time, plus they can then be
		// gotten out of sync.
		
		$this->param = (object) $this->html->_param;
		$this->server = (object) $_SERVER;
		
		// debuging
		//$this->db->debug = 99;
		
		//echo $this->db->OffsetDate(-5, $this->db->sysTimeStamp);
		//exit;
		//echo $this->db->sysTimeStamp;
		//exit;
		//echo $this->db->SQLDate('Y');
		//exit;
		/*
		echo '<pre>';
		var_dump($this->db);
		exit;
		*/
	}
	
	// get a config value
	// up to 3 levels deep
	function config( $key, $key2 = null )
	{
		
		$val = $this->config[$key];
		
		if( isset($key2) )
		{
			$val = $val[$key2];
		}
		
		return $val;
	}
	
	function set_config( $key, $val, $key2 = null )
	{
		if(isset($key2))
		{
			$key = $key . '_' . $key2;
		}
		
		$this->config[$key] = $val;
	}
	
	function _VersionCheckURL( )
	{
		$url = sprintf(
				'http://www.ledscripts.com/vcheck/vcheck.php?key=%s&ver=%s',
				'phpLedMailer',
				$this->version
		); 
		
		return $url;
	}
	
	function _LoadConfig( )
	{
		// see if we have this table
		if($this->TableExists('plm_config') == false)
		{
			return false;
		}
	
		$f_config = array( );
		
		// read in from config table
		$res = $this->db->Execute("select conf_key, conf_val from plm_config where conf_val is not null")
			or die($this->db->ErrorMsg( ));
		
		while($row = $res->FetchNextObj( ))
		{
			// don't want to deal with empty values
			if(empty($row->conf_val))
			{
				continue;
			}
			
			// handle they need for key recursion
			$conf = array( );
			$keys = explode(',', $row->conf_key);
			$this->_LoadConfig_rec_func( $conf, $keys, $row->conf_val );
			
			$f_config = $this->rec_array_merge($f_config, $conf);
		}
		
		//var_dump($f_config);
		//echo $f_config['version']  .  $this->version;
		if($f_config['version'] != $this->version)
		{
			echo "<font color=white>WARNING: Version in database is not the same as this software is reporting. Please rerun the data sync (under Utilites) ASAP!<br></font>";
		}
		
		$this->config = $this->rec_array_merge($this->config, $f_config);
		
		return true;
	}
	
	function _LoadConfig_rec_func( &$holder, &$keys, $val )
	{
		if($elm = array_shift($keys))
		{
			if(count($keys) > 0)
			{
				// at all other levels
				if(! is_array($holder[$elm]) )
				{
					$holder[$elm] = array( );
				}

				$this->_LoadConfig_rec_func($holder[$elm], $keys, $val);
			}
			else
			{
				// at the top (final) level, assign $val
				$holder[$elm] = $val;
			}
		}
	}
	
	/*
		Credit where it's due:
		brian at vermonster dot com
		http://us4.php.net/array_merge_recursive
		-- comments section
	*/
	function rec_array_merge($paArray1, $paArray2)
	{
		if (!is_array($paArray1) or !is_array($paArray2))
		{
			return $paArray2;
		}
		
		foreach ($paArray2 AS $sKey2 => $sValue2)
		{
			$paArray1[$sKey2] = $this->rec_array_merge(@$paArray1[$sKey2], $sValue2);
		}
		
		return $paArray1;
	}
	
	function TableExists( $table )
	{
		$dict = NewDataDictionary($this->db);
		
		$cols = $dict->MetaColumns( $table );
		
		if( empty($cols) )
		{
			// no table (yet -- need to re-sync tables)
			return false;
		}
		else
		{
			return true;
		}
	}

    function auth( $user = null, $pass = null )
	{
		if(! $user )
		{
			$user = $this->param->_auth_user;
			
			if(! $user )
			{
				$user = $_SESSION['user'];
			}
		}
		
		if(! $pass )
		{
			$pass = $this->param->_auth_pass;
		
			if(! $pass )
			{
				$pass = $_SESSION['pass'];
			}
		}
		
		$auth_tag = $_COOKIE['plm_auth_tag'];
		//var_dump($_COOKIE);
		
		if(!($user && $pass))
		{
			// maybe look it up based on a key?
			if($auth_tag)
			{
				//die("cookie: $auth_tag");
				
				// attempt to set $user and $pass
				// based on this cookie
				$out = $this->getAuthDetails( $auth_tag );
				
				if($out)
				{
					list($user, $pass) = $out;
				}
			}
		}
		
        // check the database
        if(($this->user = $this->validLogin($user, $pass)) === false)
		{
            //$this->send_auth( );
            if($user and $pass)
            {
            	$this->template->Set('login_error', 'Invalid login details');
            }
            
            return false;
        }
		else
		{
			if($this->param->remember_login)
			{
				// set an auth_tag
				$auth_tag = $this->setAuthTag($this->user->userid, $user, $pass);
			}
			
			$_SESSION['user'] = $user;
			$_SESSION['pass'] = $pass;
			
            // otherwise, true, daddy-O
            return $this->user;
        }
        
        return false;
    }
    
    // set an authorization tag (to remember login details)
    function setAuthTag( $userid, $u, $p )
    {
    	if(! $this->TableExists('plm_cryptcheck'))
    	{
    		return false;
    	}
    	
    	$crypt = $this->_getCrypt( );
    	
    	// make up a key
    	$key = uniqid('c');
    	$crypt->setkey( $key );
    	
    	// encrypt the username and password
    	$e_u = base64_encode($crypt->encrypt($u));
    	$e_p = base64_encode($crypt->encrypt($p));
    	$euserid = base64_encode($crypt->encrypt($userid));
    	
    	// insert the new record
    	$args = array(
    		'userid'	=> $userid,
    		'euserid'	=> $euserid,
    		'username'		=> $e_u,
    		'password'		=> $e_p,
    		'accessdatetime'	=> $this->db->DBTimeStamp(time())
    	);
    	
    	$this->db->AutoExecute('plm_cryptcheck', $args, 'INSERT')
    		or die($this->db->ErrorMsg( ));
    		
    	// set the cookie
    	setcookie(
    		'plm_auth_tag',
    		$userid . ':' . $key, // userid:key
    		time() + 157680000 // 5 years
    	);
    	
    	//die($key);
    		
    	return $key;
    }
    
    // fetch the username and password based on
    // and auth_tag
    function getAuthDetails( $key )
    {
    	if(! $this->TableExists('plm_cryptcheck'))
    	{
    		return false;
    	}
    	
    	// poll the database
    	list($userid, $key) = explode(':', $key, 2);
    	
    	// invalid cookie
    	if(! $key )
    	{
    		return false;
    	}
    	
    	// find the record in the datbase
    	$res = $this->db->Execute("select euserid, username, password from plm_cryptcheck where userid = ?",
    				array( intval($userid) )
    		) or die($this->db->ErrorMsg( ));
    		
    	if($res->RowCount( ) <= 0)
    	{
    		return false;
    	}
    	
		// we can now deal with this overhead
		$crypt = $this->_getCrypt( );
    	$crypt->setkey( $key );
    	
    	while($row = $res->FetchNextObj( ))
    	{
    		// pull these out of plain text
    		$euserid = base64_decode( $row->euserid );
    		$user = base64_decode( $row->username );
    		$pass = base64_decode( $row->password );
    		
    		$uid = $crypt->decrypt($euserid);
    		
    		// see if this matches a record in the database
    		if(strlen($uid) != strlen((string)intval($uid)))
    		{
    			// not a proper decryption
    			continue;
    		}
    		
    		$uid = intval($uid);
    		//$this->db->debug=99;
    		
    		$count = $this->db->GetOne("select count(*) from plm_users where userid = " . $uid);
    		
    		if($count != 1)
    		{
    			// invalid id found
    			continue;
    		}
    		
    		// otherwise, this must be the correct key
    		$u = $crypt->decrypt($user);
    		$p = $crypt->decrypt($pass);
    		
    		$record = array(
    			'accessdatetime'	=> $this->db->DBTimeStamp(time())
    		);
    		
    		$this->db->AutoExecute('plm_cryptcheck', $record, 'UPDATE',
    			"euserid = " . $this->db->qstr($row->euserid) .
    			" and username = " . $this->db->qstr($row->username) .
    			" and password = " . $this->db->qstr($row->password)
    		) or die($this->db->ErrorMsg());
    		
    		/*
    		$this->db->Execute(
    			"update plm_cryptcheck set accessdatetime = " .
    					// a nice little oracle-ism here
    					$this->db->DBTimeStamp(time()) .
    					"
    					where euserid = ? and username = ? and password = ?",
    					array( $row->euserid,
    							$row->username,
    							$row->password
    						)
    		) or die($this->db->ErrorMsg( ));
    		*/
    						
    		// Delete some older keys.
    		// at this point, if they haven't been here in a year,
    		// delete the record
    		$this->db->Execute(
    			"delete from plm_cryptcheck
    			 where accessdatetime < " .
    			 	$this->db->OffsetDate(-365, $this->db->sysTimeStamp)
    		) or die($this->db->ErrorMsg( ));
    		
    		return array($u, $p);
    	}
    	
    	return false;
    }
    
    function clearAuthTag( $key = null )
    {
    	if(! $this->TableExists('plm_cryptcheck'))
    	{
    		return false;
    	}
    	
    	//var_dump($_COOKIE);
    	if(! $key )
    	{
    		list($user, $key) = explode(':', $_COOKIE['plm_auth_tag'], 2);
    	}
    	
    	//die($key);
    	
    	if(! $key )
    	{
    		// probably never set
    		return;
    	}
    	
    	$crypt = $this->_getCrypt( );
    	$crypt->setkey( $key );
    	
    	// delete from database
    	$this->db->Execute("delete from plm_cryptcheck where euserid = ?",
    		array( base64_encode($crypt->encrypt($this->user->userid)))
    	) or die($this->db->ErrorMsg( ));
    	
    	// remove cookie
    	setcookie(
    		'plm_auth_tag',
    		'',
    		time() - 157680000 // - 5 years
    	);
    	
    	return true;
    }
    
    function _getCrypt( )
    {
    	require_once(dirname(__FILE__) . '/lib/crypt/cast128.php');
    	
    	return new cast128();
    }
    
    function loginPage( )
    {
    	// var_dump($_SESSION);
    	$this->template->loadTemplate('login', '_admin_templates/login.html');
    	
		$this->template->Set('login_action', 
			$this->link( )
		);
		
		if(! $this->template->isValSet('login_error') )
		{
			$this->template->Set('login_error', '');
		}
		
		return $this->template->Parse('login');
    }

    function validLogin( $user, $pass )
	{
        if(empty($user) and empty($pass))
		{
            return false;
		}

		// ->ErrorMsg()
		$sql = $this->db->Prepare("select userid, username from plm_users " .
				" where username = ? and password = ?");
		
		//var_dump($sql);
		$result = $this->db->Execute($sql, array($user, md5($pass)));
		
		if( !$result )
		{
			//var_dump($this->db);
			exit;
		}
			//or die("Error with login: " . $this->db->ErrorMsg());

        // valid login details?
        if( $result->RecordCount() <= 0 )
		{
            return false;
        }
		else
		{
			return $result->FetchNextObj( );
        }
    }

	// no longer used
	// but kept none the less
    function send_auth( )
	{
        header('WWW-Authenticate: Basic realm="phpLedMailer Admin Area"');
        header("HTTP/1.0 401 Unauthorized");

        echo "Invalid Login Details\n";
        exit(0);
    }
    
    // create a relative link with given arguments
    function link( $args = null, $clean = false )
    {
    	$self = $this->server->PHP_SELF;
    	
    	if($args !== false)
    	{
    		$self .= '?';
    		
    		$start = ($clean ? array( ) : $_GET);
    		if(is_array($args))
    		{
    			$start = array_merge($start, $args);
    		}
    		
    		foreach($start as $k => $v)
    		{
    			$self .= '&' . urlencode($k) . '=' . urlencode($v);
    		}
    	}
    	
    	return $self;
    }
    
    // the baseurl: http://mysite.com/
    function baseurl( )
    {
    	if($this->config['baseurl'])
    	{
    		// no verification on user-input values here
    		return $this->config['baseurl'];
    	}
    	
    	$port = $this->server->SERVER_PORT;
    	$is_ssl = $port == 443;
    	
    	$prefix = ($is_ssl ? 'https://' : 'http://');
    	
    	return sprintf("%s%s",
    			$prefix,
    			$this->server->HTTP_HOST
    		);
    }
    
    function sendNoCache( )
    {
		if(headers_sent( ))
		{
			// too late, nothing we can do here
			// except avoid more errors
			return;
		}

		// Date in the past
		header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");

		// always modified
		header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");

		// HTTP/1.1
		header("Cache-Control: no-store, no-cache, must-revalidate");
		header("Cache-Control: post-check=0, pre-check=0", false);

		// HTTP/1.0
		header("Pragma: no-cache");
    }
    
    // run a given user-defined method
    function runMethod( $m )
    {
    	if(!function_exists($m))
    	{
    		die("Unable to find function: $m");
    	}
    	
    	return call_user_func($m, $this);
    }

	function error( $msg )
	{
		$this->errstr = $msg;

		return false;
	}
	
	function errstr( )
	{
		return $this->errstr;
	}
	
	// return a (cached) mail object to the caller
	function mailer( )
	{
		if(isset($this->mailer))
		{
			return $this->mailer;
		}
		else
		{
			require_once(dirname(__FILE__) . '/lib/mail/class.phpmailer.php');
			
			//$this->mailer = new plmPHPMailer();
			$this->mailer = new PHPMailer();
			$this->mailer->SetLanguage("en", dirname(__FILE__) . '/lib/mail/');

			if($this->config['mailserv']['type'] == 'smtp')
			{
				$this->mailer->IsSMTP();
				$this->mailer->Host = $this->config['mailserv']['host']; 
			}

			$this->mailer->From = $this->config['listemail'];
			$this->mailer->FromName = $this->config['listname'];

			// recursive call to ourself here
			return $this->mailer( );
		}
	}
	
	function makeMenu( )
	{
		$ret = $ret2 = null;
		
		$items = array(
			'Main'			=> array( 'mod' => 'start' ),
			'Subscribers'	=> array( 'mod' => 'subscribers', 'a' => 'list' ),
			'Mail Items'	=> array( 'mod' => 'mailitems', 'a' => 'list' ),
			'Send Mail'		=> array( 'mod' => 'senditems', 'a' => 'list' ),
			'Users'			=> array( 'mod' => 'users', 'a' => 'list' ),
			'Configuration'			=> array( 'mod' => 'config' ),
			'Utilities'			=> array('mod' => 'utils' ),
			'Help'			=> array( 'mod' => 'help' )
		);
		
		$parts = array( );
		$pct = ceil(100 / count($items));
		foreach($items as $name => $pts)
		{
			$link = $this->link( $pts, true );
			
			$parts[] = '<td bgcolor="#66ffff" width="' . $pct . '%"
			          	onmouseover="javascript:navChangeFocus(this)"
					   	onmouseout="javascript:navChangeFocus(this)"
					   	align="center">' .
						$this->html->ahref(
							$link,
							$name
						) .
						'</td>';
			
			$this->template->Set(
				'self_link_' . strtolower($name),
				$link
			);
		}

		for($i = 0; $i < count($parts); $i++)
		{
			if($i > 0)
			{
				$ret .= '<td width="0" bgcolor="#000000"><img src="images/1x1.gif" width="2" height="1"></td>';
				$ret2 .= '<td><img src="images/1x1.gif" width="2" height="2"></td>';
			}

			$ret .= $parts[$i];
			$ret2 .= '<td><img src="images/1x1.gif" width="1" height="2"></td>';
		}
		
		return sprintf('<table width="100%%" border="0"
						cellspacing="0" cellpadding="0"><tr>%s</tr><tr bgcolor="#000000">%s</tr></table>', $ret, $ret2);
	}
	
	/*
	// menu on left
	// looks like butt
	function makeMenu( )
	{
		$ret = $ret2 = null;
		
		$items = array(
			'Main'			=> array( 'mod' => 'start' ),
			'Subscribers'	=> array( 'mod' => 'subscribers', 'a' => 'list' ),
			'Mail Items'	=> array( 'mod' => 'mailitems', 'a' => 'list' ),
			'Send Mail'		=> array( 'mod' => 'senditems', 'a' => 'list' ),
			'Users'	=> array( 'mod' => 'users', 'a' => 'list' ),
			'Utilities'			=> array('mod' => 'utils' ),
			'Help'			=> array( 'mod' => 'help' )
		);
		
		$parts = array( );
		$pct = ceil(100 / count($items));
		foreach($items as $name => $pts)
		{
			$link = $this->link( $pts, true );
			
			$pct = '100';
			
			$parts[] = '<tr><td bgcolor="#66ffff" width="' . $pct . '%"
			          	onmouseover="javascript:navChangeFocus(this)"
					   	onmouseout="javascript:navChangeFocus(this)"
					   	align="center">' .
						$this->html->ahref(
							$link,
							$name
						) .
						'</td></tr>';
			
			$this->template->Set(
				'self_link_' . strtolower($name),
				$link
			);
		}

		for($i = 0; $i < count($parts); $i++)
		{
			$ret .= '<tr><td width="0" bgcolor="#000000"><img src="images/1x1.gif" width="2" height="1"></td></tr>';
			if($i > 0)
			{
				$ret2 .= '<tr><td><img src="images/1x1.gif" width="2" height="2"></td></tr>';
			}

			$ret .= $parts[$i];
			$ret2 .= '<td><img src="images/1x1.gif" width="1" height="2"></td>';
		}
		
		return sprintf('<table width="120" border="0"
						cellspacing="0" cellpadding="0"><tr>%s</tr><tr bgcolor="#000000">%s</tr></table>', $ret, $ret2);
	}
	*/

	// see if a subscriber exists
	// based on a key => value pair,
	// and return the subscriberid from the key/value pair
	function subscriberExists( $key, $val )
	{
		$sql = $this->db->Prepare("select subscriberid from plm_subscribers where $key = ?");
		$res = $this->db->Execute($sql, array($val)) or die($this->db->ErrorMsg());
		
		if($res->RecordCount( ) <= 0)
		{
			return false;
		}
		else
		{
			$obj = $res->FetchNextObj( );
			return $obj->subscriberid;
		}
	}

	// add a subscriber (in pending mode)
	// with optional arg to send validation email
	function addPendingSubscriber( $args, $send_validation = true )
	{
		$this->db->BeginTrans( );
		
		$key = $this->subQueue( $args, 's' )
				or die("Error creating queue");
		
		if($send_validation)
		{
			if( ! $this->sendVerificationEmail( $key, 's' ) )
			{
				$this->db->RollbackTrans( );
				
				return $this->error( $this->errstr( ) );
			}
		}
		
		$this->db->CommitTrans( );
		
		return $key;
	}
	
	// delete a subscriber
	function delSubscriber( $sid, $send_validation = true )
	{
		$this->db->BeginTrans( );
		
		$sql = $this->db->Prepare("select * from plm_subscribers where subscriberid = ?");
		$res = $this->db->Execute($sql, array($sid)) or die($this->db->ErrorMsg());
		
		if($res->RecordCount( ) <= 0)
		{
			return $this->error("Invalid subscriberid");
		}
		
		$args = $res->FetchRow( );
		
		$key = $this->subQueue( $args, 'u' );
		
		if(! $key )
		{
			return $this->error("Error creating queue: " . $this->errstr( ));
		}
		
		if($send_validation)
		{
			if( ! $this->sendVerificationEmail( $key, 'u' ) )
			{
				$this->db->RollBackTrans( );
				
				return $this->error( $this->errstr( ) );
			}
		}
		
		$this->db->CommitTrans( );
		
		return $key;
	}
	
	// send either a subscribe or unsubscribe verification email
	function sendVerificationEmail( $key, $action )
	{
		$sql = $this->db->Prepare("select * from plm_sub_queue where keyhash = ?");
		$result = $this->db->Execute($sql, array($key)) or die($this->db->ErrorMsg( ));
		
		if($result->RecordCount( ) <= 0)
		{
			return $this->error("Unable to send verification email (no rows returned for key): " . 
						$this->db->ErrorMsg( ) );
		}
		
		$obj = $result->FetchNextObj( );
		$args = (array) unserialize($obj->data);
		
		$mail = $this->mailer( );
		$t = new LedTemplate( );
		$t->loadTemplate('email_verification',
			(
				$action == 's' ? 'templates/email_verification.html'
				 : 'templates/email_unsub_verification.html'
			)
		);
		$t->Set('listname', $this->config['listname']);
		$t->Set('verify_url',
			$this->baseurl( )  . dirname($this->link( false )) .
				'/verify.php?a=' . $action . '&i=' . $key
		);

		$t->Set( $args );

		$mail->Subject = sprintf("[%s] Verification E-Mail",
							$this->config['listname']
						);
		$mail->Body = $t->Parse('email_verification');
		$mail->AltBody = strip_tags($mail->Body);

		$mail->AddAddress( $args['emailaddr'],
			implode(' ', array($args['firstname'], $args['lastname']))
		);
		
		if(! $mail->Send( ) )
		{
			return $this->error("Unable to send email: " . $mail->ErrorInfo);
		}
		
		return true;
	}
	
	// verify a sub or unsubscribe of an address (with a key, passed to this)
	function verifyAddress( $key )
	{
		// better looking death message
		$die_msg = "Unable to complete verification process.  Perhaps you have already verified your address?";
		
		$sql = $this->db->Prepare("select * from plm_sub_queue where keyhash = ?");
		$result = $this->db->Execute($sql, array($key)) or die($this->db->ErrorMsg( ));
		
		if($result->RecordCount( ) <= 0)
		{
			//return $this->error("Unable to verify user (no rows returned for key: $key): " . 
			//			$this->db->ErrorMsg( ) );
			return $this->error($die_msg);
		}
		$obj = $result->FetchNextObj( );
		$args = (array) unserialize($obj->data);
		
		$this->db->BeginTrans( );
		
		if($obj->action == 's')
		{
			// sub scribe
			$args = array_merge($args,
						array(
							'pending' => 0,
							'createdatetime' => $this->db->DBTimeStamp(time())
						)
					);
			$ret = $this->db->AutoExecute('plm_subscribers', $args, 'INSERT');

			if($ret == false)
			{
				//return $this->error( "Unable to create subscriber record: " . $this->db->ErrorMsg( ) );
				return $this->error($die_msg);
			}

			$sid = db_insert_id( $this->db, 'plm_subscribers', 'subscriberid' );
		}
		else
		{
			// unsubscribe user
			$sql = $this->db->Prepare("delete from plm_subscribers where subscriberid = ?");
			$this->db->Execute($sql, array(intval($args['subscriberid'])))
				or die($this->db->ErrorMsg( ));
		}
		
		// delete the old record now
		$sql = $this->db->Prepare("delete from plm_sub_queue where keyhash = ?");
		$this->db->Execute($sql, array($key))
			or die($this->db->ErrorMsg( ));
		
		$this->db->CommitTrans( );
		
		return true;
	}

	// create a subscribe or unsubscrib queue
	function subQueue( $args, $action = 's' )
	{
		if(! is_array( $args ) )
		{
			return $this->error("First argument must be an array");
		}

		// random, unique key
		$key = md5(uniqid(chr(rand(65,122)) . chr(rand(65,122))));

		/*
		$ret = $this->db->insert(
			'plm_sub_queue',
			array(
				'keyhash'	=> $key,
				'data'		=> $this->db->escape(serialize($args)),
				'action'	=> $action
			),
			array(
				'queuedatetime'	=> 'now()'
			)
		) or die($this->db->errstr( ));
		*/
		//echo $this->db->qstr($this->db->qstr($this->db->DBTimeStamp(time())));
		$record = array(
			'keyhash'	=> $key,
			'data'		=> serialize($args),
			'action'	=> $action,
			'queuedatetime'	=> $this->db->DBTimeStamp(time())
		);
		$ret = $this->db->AutoExecute('plm_sub_queue', $record, 'INSERT')
			or die($this->db->ErrorMsg());
		
		return $key;
	}

	// queue a message (mid) to one or all subscribers
	function queueToSubscribers( $mid, $sid = null )
	{
		$mid = intval($mid);
		
		if(! $mid )
		{
			return $this->error("Invalid mid!");
		}

		$where = null;

		if(isset($sid))
		{
			$where = " and subscriberid = $sid ";
		}
		
		/*
		$this->db->queryNoExec(
			"delete from plm_message_queue
				where messageid = " . $mid . $where
		) or die($this->db->errstr( ));
		*/
		$sql = "delete from plm_message_queue where messageid = " . $mid . $where;
		$this->db->Execute($sql) or die($this->db->ErrorMsg( ));
		
		$sql = sprintf(
			"insert into plm_message_queue
				select %d, subscriberid, 0, null
				from plm_subscribers
				where 1=1 $where",
			$mid
		);
		
		$this->db->Execute($sql) or die($this->db->ErrorMsg( ));

		return true;
	}

	// get a message object
	// based on a messageid
	function getMailMessage( $mid )
	{
		$sql = sprintf("
				select messageid, subject, content, contentplain
				from plm_messages
				where messageid = %d
			", $mid
		);

		$result = $this->db->Execute( $sql )
					or die($this->db->errstr( ));

		if($result->RecordCount( ) <= 0)
		{
			return $this->error("Unable to find any message that match that mid!");
		}

		return new plmMailMessage( $this, $result->FetchNextObj( ) );
	}
	
	// simply returns true or false
	// based on a regex
	function ValidEmailFormat( $email )
	{
		// email validation
		// taken from an asp example, but probably 
		// works pretty well anyway :)
		// regEx.Pattern = "^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$"
		
		$regex = '/^[a-z][\w\.-]*[a-z0-9]@[a-z0-9][\w\.-]*[a-z0-9]\.[a-z][a-z\.]*[a-z]$/i';
		
		return preg_match($regex, $email) ? true : false;
	}
	
	function cleanDeath( $msg )
	{
		$this->template->loadTemplate('die',
			dirname(__FILE__) . '/templates/die.html');
			
		$this->template->Set('die_reason', $msg);
		
		die($this->template->Parse('die'));
	}
	
	// just a shortcut to determine if
	// php is running in safe mode
	function _safeMode( )
	{
		return ini_get('safe_mode');
	}
	
	// rebuild the database layout
	// put here so that it can be called from
	// install.php as well as anything else that would
	// make use of the common class
	// optional param $do_executes will actually execute all the code
	function syncDBLayout( $do_executes = false )
	{
		// bad idea to not ignore user aborts
		ignore_user_abort(true);
		if($this->_safeMode( ))
		{
			set_time_limit(0); // no timeout (besides what the webserver does)
		}
		
		$ret = array();
		
		$force_type = null;
		
		// Allow sqlite to be created correctly
		if($this->db->databaseType == 'sqlitepo')
		{
			$force_type = 'sqlite';
		}

		// yet another RIG for mysql
		// without setting $_db_type = mysql
		// it wont append a (stupid) key() entry to the create table
		// for plm_message_queue for subscriberid
		// because innodb foreign keys require
		// an index for each field (in the child and parent tables)
		// , with that field as the *first* item of it
		// so we have to make up an index for this in mysql
		$_db_type = $this->db->dataProvider;
		require_once(dirname(__FILE__) . '/db_schema.php');
	
		if(!is_array($meta_tables)
			&& !is_array($meta_indexes))
		{
			return $this->error("Unable to find meta tables or indexes!");
		}
		
		// create dictionary object
		$dict = NewDataDictionary($this->db, $force_type);

		if(is_array($meta_tables))
		{
			foreach($meta_tables as $table => $vals)
			{
				//$sql = $dict->CreateTableSQL($table,
				$sql = $dict->ChangeTableSQL($table,
						$vals[0],
						$vals[1]
					);

				for($i = 0; $i < count($sql); $i++)
				{
					$ret[] = str_replace(';;', ';', $sql[$i]);
				}
			}
		}

		// next we need to do meta_indexes
		if(is_array($meta_indexes))
		{
			foreach($meta_indexes as $table => $vals)
			{
				foreach($vals as $index => $opts)
				{
					// drop first
					$sql = $dict->DropIndexSQL( $index, $table );

					for($i = 0; $i < count($sql); $i++)
					{
						$ret[] = $sql[$i];
					}

					$sql = $dict->CreateIndexSQL(
							$index,
							$table,
							$opts[0],
							$opts[1]
						);

					for($i = 0; $i < count($sql); $i++)
					{
						$ret[] = $sql[$i];
					}
				}
			}
		}
		
		// execute these now?
		if($do_executes)
		{
			// does this have a version value at all?
			// if not, then we assume this is the install step
			// and we need to init a bunch of values
			$r = $this->_install_config_values( );
			if(is_array($r))
			{
				$ret = array_merge($ret, $r);
			}
			
			if(! $this->isSetup( ) )
			{
				// nada
			}
			
			// add one more query to the list!
			// this ensures that the current version
			// is always in the database
			$ret[] = "delete from plm_config where conf_key = 'version'";
			$ret[] = "insert into plm_config values ('version', '$this->version')";
			
			$return = '';
			foreach($ret as $s)
			{
				$return .= "<b>Executing:</b>\n<br>$s;\n<br>";
				
				// only make notes of errors
				// don't die on them
				if(! $this->db->Execute($s) )
				{
					$return .= "<font color=red>Error: " . $this->db->ErrorMsg( ) . "</font>";
				}
				else
				{
					$return .= "<font color=green>Successful</font>";
				}
				
				$return .= "\n\n<br><br>";
			}
			
			return $return;
		}
		else
		{
			return $ret;
		}
	}
	
	function isSetup( )
	{
		$count = $this->db->GetOne("select count(*) from plm_config where conf_key = 'version'");
		
		return ($count <= 0 ? false : true);
	}
	
	// addon to the above items

	// this is where core-base-defaults
	// are defined (hard-coded, infact)
	function _install_config_values( )
	{
		$ret = array( );
		$insert_values = array(
			'testing' => 0,
			'baseurl' => '', // no defaults for now
			'itemsetsize' => 25,
			'listemail' => 'hide@address.com',
			'listname' => 'My Site\'s Mailing List',
			'mailserv,host' => 'localhost',
			'mailserv,type' => 'standard',
			'require_email_validation' => 0,
			'sendsize' => 10,
			'ufnames,userfield1' => 'City',
			'ufnames,userfield2' => 'State',
			//'imageuploads'	=> 1,
			'auto_version_check'	=> 1,
			'validate_email_regex'	=> 1,
			'webservice_key'	=> ''
		);

		// for new key values
		$exists = $this->TableExists('plm_config');
		
		// do the inserts
		// and ignore any errors (since the values)
		// may already exist, and we don't want to overwrite them
		foreach($insert_values as $k => $v)
		{
			$c = 0;
			
			if($exists)
			{
				// this could reset it to 1
				// but is only run if the table exists
				$c = $this->db->GetOne("select count(*) from plm_config where conf_key = '$k'");
			}

			// we want this to happen if
			// the table doesn't exist, or the key doesn't exist
			if($c <= 0)
			{
				$ret[] = sprintf("insert into plm_config (conf_key, conf_val) values (%s, %s)",
							$this->db->qstr( $k ),
							$this->db->qstr( $v )
						);
			}
		}
		
		return $ret;
	}
	
	// returns an object that represents a nusoap-style
	// server handle
	function Get_phpLedMailer_SOAPService( )
	{
		require_once(dirname(__FILE__) . '/lib/soap/class.phpLedMailer_SOAPService.php');
		
		return new phpLedMailer_SOAPService( $this );
	}
	
	// get the current web service key
	function GetWebServiceKey( )
	{
		// should actually get pulled in at runtime
		return $this->config['webservice_key'];
		
		if($this->TablExists('plm_config'))
		{
			$key = $this->db->GetOne("select conf_val from plm_config where conf_key = 'webservice_key'");
			
			if($key)
			{
				return $key;
			}
		}
		
		return false;
	}
	
	// this will only work in php5+
	function __destruct( )
	{
		if(is_object($this->db))
		{
			$this->db->Close();
		}
	}
}

// a mail message that is ready to be sent to the users
// note: this is the user-created e-mails, not things like
// verifiation e-mails.
class plmMailMessage
{
	var $mail = null;
	var $p = null;
	var $message = null;
	var $current_tags = array( );
	
	function plmMailMessage( &$parent, $message )
	{
		$this->p = &$parent;
		$this->mail = $this->p->mailer( );
		$this->message = $message;
	}

	function SetupMessage( )
	{
		$this->mail->Subject = $this->Parse( $this->message->subject );
		$this->mail->Body = $this->Parse( $this->message->content );
		
		$plain = $this->message->contentplain;

		// default to a strip'd version of the above
		if(empty($plain))
		{
			$plain = strip_tags( $this->message->content );
		}

		$this->mail->AltBody = $this->Parse( $plain );
	}

	function sendTo( $addr, $name = null, $tags = null )
	{
		if($this->p->config['testing'])
		{
			return true;
		}
		
		// {firstname}... tags
		if(!is_array($tags))
		{
			$tags = array( );
		}
		
		// set these to be used in parse()
		$this->current_tags = $tags;

		if(!isset($name)
		   && !is_array($addr))
		{
			die("invalid sendTo() usage " . __LINE__);
		}

		$this->mail->ClearAddresses( );

		if(is_array($addr))
		{
			foreach($addr as $email => $name)
			{
				// call this function again
				$this->sendTo( $email, $name );
			}
		}
		else
		{
			// set the address
			$this->mail->AddAddress( $addr, trim($name) );
		}

		// Call this to set the mail objects
		// sub as subject and body
		// and also to parse tags ({firstname} {lastname}) type stuff
		$this->SetupMessage( );

		// send the actual email
		if( ! $this->mail->Send( ) )
		{
			return $this->p->error( $this->mail->ErrorInfo );
		}
		else
		{
			return true;
		}
	}
	
	// parse the tags ({tagname})
	// into the given $msg value that is
	// passed in
	// -- this is generally the subject and body
	// of the message
	function Parse( $msg )
	{
		$tags = $this->current_tags;
		
		// invalid list of tags sent in
		if(!is_array($tags))
		{
			return $msg;
		}
		
		// the actual replacement logic
		foreach($tags as $k => $v)
		{
			$msg = str_replace('{' . $k . '}', $v, $msg);
		}
		
		return $msg;
	}

	function errstr( )
	{
		return $this->p->errstr( );
	}
}

class plmDataSync
{
	var $p = null; // parent object
	
	function plmDataSync( &$p )
	{
		$this->p = &$p;
	}
	
	function isSyncing( $bit = null )
	{
		if( isset($bit) )
		{
			// delete no matter what
			$this->p->db->Execute("delete from plm_config where conf_key = 'in_db_sync'")
				or die($this->p->db->ErrorMsg( ));
			
			if($bit)
			{
				// insert into the database if running
				$this->p->db->Execute("insert into plm_config (conf_key, conf_val) values ('in_db_sync', '$bit')")
					or die($this->p->db->ErrorMsg( ));
			}
				
			return true;
		}
		else
		{
			// else, return the current value, if at all
			$r = $this->p->db->GetOne("select conf_val from plm_config where conf_key = 'in_db_sync'");
			
			return ($r ? true : false);
		}
	}
	
	function runSync( $bit = null )
	{
		$this->isSyncing(true);
		
		$ret = $this->p->syncDBLayout( $bit );
		
		$this->isSyncing(false);
		
		return $ret;
	}
}

// this is thanks to postgresql and seq names
// and oracle (also using sequences)
// we can probably apply this to about any database type
function db_insert_id( $db, $table = null, $col = null )
{
	if(strtolower($db->dataProvider) == "postgres")
	{
		return $db->pg_insert_id( $table, $col );
	}
	else
	{
		if($db->dataProvider == 'oci8')
		{
			// the current value for this table's sequence
			$sql = sprintf("SELECT (%s.currval) FROM DUAL",
					'SEQ_' . strtoupper($table));
			$id =  $db->GetOne( $sql );
			
			if($id === false)
			{
				// some error
				// perhaps let them know?
			}
			
			return  $id;
		}
		else
		{
			return $db->Insert_ID( );
		}
	}
}

?>
Return current item: phpLedMailer