![]() |
Xataface Email Module 0.3
Email/Mailmerge Module for Xataface
|
00001 <?php 00002 /*------------------------------------------------------------------------------- 00003 * Xataface Web Application Framework 00004 * Copyright (C) 2005-2008 Web Lite Solutions Corp (shannah@sfu.ca) 00005 * 00006 * This program is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU General Public License 00008 * as published by the Free Software Foundation; either version 2 00009 * of the License, or (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License 00017 * along with this program; if not, write to the Free Software 00018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00019 *------------------------------------------------------------------------------- 00020 */ 00021 00022 00033 class actions_email { 00034 00035 var $messages = array(); 00036 var $emailTable; 00037 var $joinTable; 00038 var $recipientsTable; 00039 var $emailColumn; 00040 00044 function handle(&$params){ 00045 $action =& $params['action']; 00046 //print_r($params); 00047 $app =& Dataface_Application::getInstance(); 00048 $query =& $app->getQuery(); 00049 $query['-skip'] = 0; 00050 $query['-limit'] = 9999999; 00051 00052 // Let's validate some of the parameters first 00053 00054 // The actions.ini file should define an email_column and email_table parameters 00055 // to indicate: 00056 // a. the name of the column from the current table that should be used 00057 // as the "send to" email address. 00058 // b. the name of the table that should store the email messages. 00059 00060 00061 import('Dataface/Ontology.php'); 00062 Dataface_Ontology::registerType('Person', 'Dataface/Ontology/Person.php', 'Dataface_Ontology_Person'); 00063 $ontology =& Dataface_Ontology::newOntology('Person', $query['-table']); 00064 $action['email_column'] = $ontology->getFieldname('email'); 00065 00066 00067 00068 if ( !@$action['email_column'] ) return PEAR::raiseError("No email column specified in actions.ini", DATAFACE_E_WARNING); 00069 if ( !@$action['email_table'] ) return PEAR::raiseError("No email table specified in actions.ini", DATAFACE_E_WARNING); 00070 00071 // Make sure the table and column names are not malicious. 00072 $this->emailColumn = $col = $action['email_column']; 00073 if ( strpos($col, '`') !== false ) return PEAR::raiseError("Invalid email column name: '$col'", DATAFACE_E_WARNING); 00074 00075 $this->emailTable = $table = $action['email_table']; 00076 if ( strpos($table, '`') !== false ) return PEAR::raiseError("Invalid email table name: '$table'", DATAFACE_E_WARNING); 00077 00078 $this->joinTable = $join_table = 'xataface__email_log';//$query['-table'].'__email'; 00079 $join_table = $this->joinTable; 00080 $this->recipientsTable = $query['-table']; 00081 // The name of the table that tracks which records have had email sent. 00082 00083 // Next make sure that the email table(s) exist(s) 00084 if ( !Dataface_Table::tableExists($table, false) || !Dataface_Table::tableExists($join_table, false) ){ 00085 $this->createEmailTables($table, $join_table); 00086 } 00087 00088 $emailTableObj =& Dataface_Table::loadTable($this->emailTable); 00089 $contentField =& $emailTableObj->getField('content'); 00090 $contentField['widget']['atts']['rows'] = 20; 00091 $contentField['widget']['atts']['cols'] = 60; 00092 $contentField['widget']['label'] = 'Message body'; 00093 $contentField['widget']['description'] = 'Please enter your message content in plain text.'; 00094 $contentField['widget']['type'] = 'ckeditor'; 00095 $contentField['widget']['ckeditor']['toolbar'] = 'XBasic'; 00096 $contentField['widget']['ckeditor']['extraPlugins'] = 'SchemaBrowser'; 00097 //$contentField['widget']['atts']['data-xf-schemabrowser-tablename'] = $query['-table']; 00098 Dataface_JavascriptTool::getInstance()->import('xataface/modules/ckeditor/plugins/SchemaBrowser.js'); 00099 00100 //$contentField['widget']['editor'] = 'ckeditor'; 00101 00102 $subjectField =& $emailTableObj->getField('subject'); 00103 $subjectField['widget']['atts']['size'] = 60; 00104 00105 $fromField =& $emailTableObj->getField('from'); 00106 $fromField['widget']['atts']['size'] = 60; 00107 $fromField['widget']['description'] = 'e.g. Web Lite Solutions <info@weblite.ca>'; 00108 00109 00110 $ccField =& $emailTableObj->getField('cc'); 00111 $ccField['widget']['atts']['size'] = 60; 00112 00113 $ignoreBlacklistField =& $emailTableObj->getField('ignore_blacklist'); 00114 $ignoreBlacklistField['widget']['type'] = 'checkbox'; 00115 $ignoreBlacklistField['widget']['description'] = 'The black list is a list of email addresses that have opted out of receiving email. I.e. Users on the black list do not want to receive email. Check this box if you want to send to blacklisted addresses despite their wish to be left alone.'; 00116 00117 00118 $templateField =& $emailTableObj->getField('template_id'); 00119 $templateField['widget']['filters']['table_name'] = $query['-table']; 00120 00121 $form = df_create_new_record_form($table); 00122 $form->_build(); 00123 00124 $form->addElement('hidden','-action'); 00125 $form->addElement('hidden','-table'); 00126 $form->setDefaults(array('-action'=>$query['-action'], '-table'=>$query['-table'])); 00127 $form->insertElementBefore($form->createElement('checkbox', 'send_now', '','Send now (leave this box unchecked if you wish these emails to be queued for later sending by the daily cron job. Recommended to leave this box unchecked for large found sets (>100 records).)'),'submit_new_newsletters_record'); 00128 $form->addElement('hidden', '-query_string'); 00129 $form->setDefaults(array('-query_string'=>base64_encode(serialize($query)))); 00130 $form->setSubmitLabel("Add to Mail Queue"); 00131 if ( @$app->_conf['from_email'] ){ 00132 $form->setDefaults(array('from'=>$app->_conf['from_email'])); 00133 } 00134 00135 00136 00137 if ( $form->validate() ){ 00138 $res = $form->process(array(&$form,'save'), true); 00139 if ( PEAR::isError($res) ) return $res; 00140 00141 // The form saved ok.. so we can send the emails. 00142 $vals = $form->exportValues(); 00143 $q2 = unserialize(base64_decode($vals['-query_string'])); 00144 $qb = new Dataface_QueryBuilder($query['-table'], $q2); 00145 $recTable = Dataface_Table::loadTable($query['-table']); 00146 00147 $tkeys = $recTable->keys(); 00148 $keyCol = null; 00149 foreach ($tkeys as $key=>$val){ 00150 $keyCol = $key; 00151 break; 00152 } 00153 $sql = "insert ignore into `$join_table` (recipient_email,messageid,date_created,primary_key) select distinct (`".$col."`) `".$col."`, '".$form->_record->val('id')."' as messageid, now() as date_created, `".$keyCol."` as primary_key ".$qb->_from()." ".$qb->_secure($qb->_where()); 00154 00155 //echo $sql;exit; 00156 $sres = df_q($sql); 00157 00158 00159 //if ( !$sres ) trigger_error(mysql_error(df_db()), E_USER_ERROR); 00160 $jobId = $this->postJob($form->_record->val('id'), $this->emailTable, $this->joinTable, $this->recipientsTable, $this->emailColumn); 00161 $q2['-action'] = 'email_progress'; 00162 $q2['-job-id'] = $jobId; 00163 unset($q2['-limit']); 00164 header('Location: '.$app->url($q2).'&--msg='.urlencode("The message has been queued for delivery")); 00165 exit; 00166 00167 00168 } 00169 00170 $addresses = array(); 00171 00172 ob_start(); 00173 $form->display(); 00174 $context = array(); 00175 $context['email_form'] = ob_get_contents(); 00176 $profileTable =& Dataface_Table::loadTable($query['-table']); 00177 00178 $context['fields'] = array_keys($profileTable->fields(false,true,true)); 00179 $modurl = DATAFACE_SITE_URL.'/modules/Email'; 00180 if ( realpath(__FILE__) == realpath(DATAFACE_PATH.'/modules/Email/email.php') ){ 00181 $modurl = DATAFACE_URL.'/modules/Email'; 00182 } 00183 00184 $context['EMAIL_ROOT'] = $modurl; 00185 00186 ob_end_clean(); 00187 df_register_skin('email', dirname(__FILE__).'/../templates'); 00188 df_display($context, 'email_form.html'); 00189 00190 00191 } 00192 00193 function isBlackListed($email){ 00194 if ( !Dataface_Table::tableExists('dataface__email_blacklist') ) $this->createEmailTables(null,null); 00195 $res = mysql_query("select email from dataface__email_blacklist where email='".addslashes($email)."' limit 1", df_db()); 00196 if ( !$res ) trigger_error(mysql_error(df_db()), E_USER_ERROR); 00197 list($num) = mysql_fetch_row($res); 00198 @mysql_free_result($res); 00199 return $num; 00200 } 00201 00202 function getBlackListed($emails){ 00203 if ( !Dataface_Table::tableExists('dataface__email_blacklist') ) $this->createEmailTables(null,null); 00204 if ( !is_array($emails) ) $emails = array($emails); 00205 $res = mysql_query("select email from dataface__email_blacklist where email in ('".implode("','", array_map('addslashes',$emails))."')", df_db()); 00206 $out = array(); 00207 if (!$res ) trigger_error(mysql_error(df_db()), E_USER_ERROR); 00208 while ($row = mysql_fetch_row($res) ) $out[] = $row[0]; 00209 @mysql_free_result($res); 00210 return $out; 00211 } 00212 00221 function createEmailTables($tablename, $join_table){ 00222 $app =& Dataface_Application::getInstance(); 00223 00224 $sql = array(); 00225 if ( isset($tablename) ){ 00226 $sql[] = "create table if not exists `{$tablename}` ( 00227 `id` int(11) not null auto_increment, 00228 `template_id` int(11) default null, 00229 `subject` varchar(128) not null, 00230 `cc` varchar(128) default null, 00231 `from` varchar(128) default null, 00232 `content` text, 00233 `ignore_blacklist` tinyint(1) default 0, 00234 posted_by varchar(255) default null, 00235 primary key (`id`)) DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"; 00236 } 00237 if ( isset($join_table) ){ 00238 $sql[] = "create table if not exists `{$join_table}` ( 00239 `messageid` int(11) not null, 00240 `recipient_email` varchar(128) not null, 00241 `sent` tinyint(1) default 0, 00242 `date_created` datetime default null, 00243 `date_sent` datetime default null, 00244 `success` tinyint(1) default null, 00245 `comment` varchar(255) default null, 00246 primary key (`messageid`,`recipient_email`))"; 00247 } 00248 00249 00250 $sql[] = "create table if not exists `dataface__email_blacklist` ( 00251 `email` varchar(255) not null primary key 00252 )"; 00253 00254 foreach ($sql as $q ){ 00255 $res = mysql_query($q, $app->db()); 00256 if ( !$res ) trigger_error(mysql_error($app->db()), E_USER_ERROR); 00257 } 00258 00259 try { 00260 // The comment field was added in version 2.0... adding it now 00261 // just in case there was a legacy table that we are adding to. 00262 df_q("alter table `{$join_table}` add `comment` varchar(255) after `success`"); 00263 } catch (Exception $ex){} 00264 return true; 00265 00266 00267 00268 } 00269 00270 00271 private $_tempRecipientRecord; 00277 function _replaceFieldCallback($matches){ 00278 return $this->_tempRecipientRecord->display($matches[1]); 00279 00280 } 00281 00282 00283 function getAttachments($emailId){ 00284 $res = df_q("select file from xataface__email_attachments where email_id='".addslashes($emailId)."'"); 00285 00286 $out = array(); 00287 00288 $field =& Dataface_Table::loadTable('xataface__email_attachments')->getField('file'); 00289 $savepath = $field['savepath']; 00290 if ( !$savepath || !is_dir($savepath) ){ 00291 return array(); 00292 } 00293 $ajaxUploadMod = Dataface_ModuleTool::getInstance()->loadModule('modules_ajax_upload'); 00294 if ( !$ajaxUploadMod || PEAR::isError($ajaxUploadMod) ){ 00295 error_log("Could not send attachments because the ajax_upload module is not loaded."); 00296 return array(); 00297 } 00298 00299 while ($row = mysql_fetch_row($res) ){ 00300 00301 $fileName = basename($row[0]); 00302 $filePath = $savepath . DIRECTORY_SEPARATOR. $fileName; 00303 if ( !file_exists($filePath) ){ 00304 error_log("Failed to attach file $filePath because it could not be found."); 00305 continue; 00306 } 00307 $contents = file_get_contents($filePath); 00308 $mimetype = $ajaxUploadMod->getMimeType($filePath); 00309 $attachment = MIME::message($contents, $mimetype, $fileName, 'ISO-8859-1', 'base64', 'attachment'); 00310 $out[] = $attachment; 00311 } 00312 @mysql_free_result($res); 00313 return $out; 00314 } 00315 00316 00325 function sendMail($emailId, $emailTable=null, $joinTable = null, $recipientsTable = null , $emailColumn = null){ 00326 require_once dirname(__FILE__).'/../lib/XPM/MIME.php'; 00327 if ( isset($emailTable) ) $this->emailTable = $emailTable; 00328 if ( isset($joinTable) ) $this->joinTable = $joinTable; 00329 if ( isset($recipientsTable) ) $this->recipientsTable = $recipientsTable; 00330 if ( isset($emailColumn) ) $this->emailColumn = $emailColumn; 00331 $app =& Dataface_Application::getInstance(); 00332 $conf =& $app->_conf; 00333 00334 // We want to be able to override the replacement context 00335 // via a delegate class method. 00336 $decorateEmailContextFunc = 'decorateEmailContext'; 00337 $recipientsTableObj = Dataface_Table::loadTable($this->recipientsTable); 00338 $recipientsTableDelegate =& $recipientsTableObj->getDelegate(); 00339 $appDelegate =& $app->getDelegate(); 00340 $tableDecorateEmailContextFuncExists = (isset($recipientsTableDelegate) and method_exists($recipientsTableDelegate, $decorateEmailContextFunc) ); 00341 00342 $appDecorateEmailContextFuncExists = (isset($appDelegate) and method_exists($appDelegate, $decorateEmailContextFunc)); 00343 00344 00345 00346 if ( @$conf['_mail']['func'] ) $mail_func = $conf['_mail']['func']; 00347 else $mail_func = 'mail'; 00348 00349 $emailTableObj =& Dataface_Table::loadTable($this->emailTable); 00350 00351 $recTable = Dataface_Table::loadTable($this->recipientsTable); 00352 00353 $tkeys = $recTable->keys(); 00354 $keyCol = null; 00355 foreach ($tkeys as $key=>$val){ 00356 $keyCol = $key; 00357 break; 00358 } 00359 00360 $emailTableObj->addRelationship('recipients', 00361 array('__sql__' => 'select * from `'.$this->recipientsTable.'` r inner join `'.$this->joinTable.'` j on (`r`.`'.$this->emailColumn.'` = j.recipient_email and `r`.`'.$keyCol.'`= j.primary_key) inner join `'.$this->emailTable.'` e on e.id = j.messageid where e.id=\''.addslashes($emailId).'\'') 00362 ); 00363 00364 00365 $email = df_get_record($this->emailTable, array('id'=>$emailId)); 00366 if ( !$email) return PEAR::raiseError("Failed to send email because no message with id {$emailId} could be found.", DATAFACE_E_ERROR); 00367 00368 $jres = df_q("select job_id from xataface__email_jobs where email_id='".addslashes($emailId)."'"); 00369 if ( mysql_num_rows($jres) == 0 ){ 00370 throw new Exception("Could not find job associated with email."); 00371 } 00372 list($jobId) = mysql_fetch_row($jres); 00373 @mysql_free_result($jres); 00374 00375 $template = null; 00376 if ( $email->val('template_id') ){ 00377 $template = df_get_record('xataface__email_templates', array('template_id'=>'='.$email->val('template_id'))); 00378 } 00379 00380 // Let's update the count 00381 $totalRecipients = $email->numRelatedRecords('recipients'); 00382 $res = df_q("update xataface__email_jobs set total_emails='".addslashes($totalRecipients)."' where email_id='".addslashes($emailId)."'"); 00383 00384 00385 $recipients = $email->getRelatedRecordObjects('recipients', 0,500, 'sent=0'); 00386 foreach ($recipients as $recipient ){ 00387 00388 00389 // Check to make sure that job hasn't been cancelled. 00390 $jres = df_q("select cancelled from xataface__email_jobs where job_id='".addslashes($jobId)."'"); 00391 if ( mysql_num_rows($jres) == 0 ){ 00392 throw new Exception("Could not find job record. Must have been cancelled."); 00393 } 00394 list($cancelled) = mysql_fetch_row($jres); 00395 @mysql_free_result($res); 00396 if ( $cancelled ){ 00397 return false; 00398 } 00399 //sleep(5); 00400 00401 00402 $recipientObj = $recipient->toRecord(); 00403 00404 00405 //$values = $recipient->strvals(); 00406 if ( $appDecorateEmailContextFuncExists ){ 00407 $appDelegate->$decorateEmailContextFunc($email, $template, $recipientObj); 00408 } 00409 00410 if ( $tableDecorateEmailContextFuncExists ){ 00411 $recipientsTableDelegate->$decorateEmailContextFunc($email, $template, $recipientObj); 00412 } 00413 00414 00415 00416 00417 $keys = array(); 00418 //foreach ($values as $key=>$val) $keys[] = '/%'.$key.'%/'; 00419 //$values = array_values($values); 00420 //$content = preg_replace($keys, $values, $recipient->strval('content')); 00421 $this->_tempRecipientRecord = $recipientObj; 00422 $content = preg_replace_callback('/\{\$([^\}]+)\}/', array($this, '_replaceFieldCallback'), $recipient->strval('content')); 00423 00424 $uniqid = uniqid(); 00425 df_q("update xataface__email_log set uniqid='".addslashes($uniqid)."' where log_id='".addslashes($recipient->val('log_id'))."'"); 00426 00427 00428 $opt_out_url = df_absolute_url(DATAFACE_SITE_HREF.'?-action=email_opt_out&email='.urlencode($uniqid)); 00429 00430 $html_content = $content .= <<<END 00431 <hr /> 00432 <p>If you don't want to receive email updates from us, you can opt out of our mailing list by clicking <a href="$opt_out_url">here</a> .</p> 00433 END; 00434 00435 $content .= <<<END 00436 00437 ------------------------------------------------------------------ 00438 If you don't want to receive email updates from us, you can opt out of our mailing list by going to $opt_out_url . 00439 END; 00440 00441 $headers = array(); 00442 00443 if ( trim($email->strval('cc')) ){ 00444 $headers[] = "Bcc: ".$email->strval('cc'); 00445 } 00446 00447 if ( trim($email->strval('from')) ){ 00448 $headers[] = "From: ".$email->strval('from'); 00449 $headers[] = "Reply-to: ".$email->strval('from'); 00450 } 00451 00452 if ( @$app->_conf['mail_host'] ){ 00453 $headers[] = 'Message-ID: <' . md5(uniqid(time())) . '@'.$app->_conf['mail_host'].'>'; 00454 } 00455 //$headers[] = "Content-Type: text/plain; charset=".$app->_conf['oe']; 00456 00457 $joinRecord = $recipient->toRecord($this->joinTable); 00458 00459 if ( !trim($recipient->val('recipient_email')) ){ 00460 $joinRecord->setValue('success',0); 00461 $joinRecord->setValue('sent',1); 00462 $joinRecord->setValue('comment', 'Blank address'); 00463 $joinRecord->save(); 00464 unset($joinRecord); 00465 unset($recipient); 00466 continue; 00467 } 00468 00469 00470 // path to 'MIME.php' file from XPM4 package 00471 00472 00473 // get ID value (random) for the embed image 00474 $id = MIME::unique(); 00475 00476 // set text/plain version of message 00477 $text = MIME::message(htmlspecialchars_decode(strip_tags(preg_replace(array('/<br[^>]*>/i','/<div[^>]*>/i','/<p[^>]*>/i', '/<table[^>]*>/i'), array("\r\n","\r\n","\r\n","\r\n"),$content))), 'text/plain', null, $app->_conf['oe']); 00478 // set text/html version of message 00479 $html = MIME::message($html_content, 'text/html', null, $app->_conf['oe']); 00480 // add attachment with name 'file.txt' 00481 //$at[] = MIME::message('source file', 'text/plain', 'file.txt', 'ISO-8859-1', 'base64', 'attachment'); 00482 $at = $this->getAttachments($emailId); 00483 //$file = 'xpertmailer.gif'; 00484 // add inline attachment '$file' with name 'XPM.gif' and ID '$id' 00485 //$at[] = MIME::message(file_get_contents($file), FUNC::mime_type($file), 'XPM.gif', null, 'base64', 'inline', $id); 00486 00487 // compose mail message in MIME format 00488 //print_r($html); 00489 //print_r($text); 00490 //exit; 00491 $mess = MIME::compose($text, $html, $at); 00492 00493 $le = defined('PHP_EOL') ? PHP_EOL : "\n"; 00494 00495 00496 if ( !$email->val('ignore_blacklist') and $this->isBlackListed($recipient->val('recipient_email')) ){ 00497 error_log("\nEmail address '".$recipient->val('recipient_email')."' is black listed so we do not send email to this address..."); 00498 $joinRecord->setValue('success',0); 00499 $joinRecord->setValue('sent',1); 00500 $joinRecord->setValue('comment', 'Black listed'); 00501 df_q("update xataface__email_jobs set sent_emails=sent_emails+1, blacklisted_emails=blacklisted_emails+1 where email_id='".addslashes($emailId)."'"); 00502 00503 } 00504 00505 else if ( $mail_func($recipient->strval('recipient_email'), $email->strval('subject'), $mess['content'], implode($le, $headers).$le.$mess['header']) ){ 00506 $joinRecord->setValue('success',1); 00507 $joinRecord->setValue('sent',1); 00508 df_q("update xataface__email_jobs set sent_emails=sent_emails+1, successful_emails=successful_emails+1 where email_id='".addslashes($emailId)."'"); 00509 //echo "Successfully sent email to ".$recipient->val('recipient_email'); 00510 //echo "Successfully sent email to {$recipient->strval('recipient_email')}" ; 00511 //exit; 00512 } else { 00513 $joinRecord->setValue('success',0); 00514 $joinRecord->setValue('sent',1); 00515 $this->messages[] = "Failed to send email to ".$email->val('recipient_email'); 00516 error_log("Failed to send email to ".$email->val('recipient_email')); 00517 df_q("update xataface__email_jobs set sent_emails=sent_emails+1, failed_emails=failed_emails+1 where email_id='".addslashes($emailId)."'"); 00518 //echo "Failed to send"; 00519 //exit; 00520 } 00521 00522 $joinRecord->setValue('date_sent',date('Y-m-d H:i:s')); 00523 $joinRecord->save(); 00524 00525 unset($joinRecord); 00526 unset($recipient); 00527 00528 00529 } 00530 00531 } 00532 00533 function postJob($emailId, $emailTable=null, $joinTable = null, $recipientsTable = null , $emailColumn = null){ 00534 00535 $res = df_q("select count(*) from `$joinTable` where messageid='".addslashes($emailId)."'"); 00536 list($count) = mysql_fetch_row($res); 00537 //echo "Posting job to join table: $joinTable with count ".$count;exit; 00538 00539 $res = df_q( 00540 "insert into xataface__email_jobs ( 00541 email_id, 00542 email_table, 00543 join_table, 00544 recipients_table, 00545 email_column, 00546 active, 00547 total_emails, 00548 start_time 00549 ) 00550 values ( 00551 '".addslashes($emailId)."', 00552 '".addslashes($emailTable)."', 00553 '".addslashes($joinTable)."', 00554 '".addslashes($recipientsTable)."', 00555 '".addslashes($emailColumn)."', 00556 1, 00557 '".addslashes($count)."', 00558 '".time()."' 00559 )"); 00560 return mysql_insert_id(df_db()); 00561 00562 00563 } 00564 } 00565 00566 00567 ?>