File: D:/HostingSpaces/PvdBoogaard/indoorski.nl/backup/oude-site/marketing/admin/functions/bounce.php
<?php
/**
* This file has the bounce management pages in it.
*
* @version $Id: bounce.php,v 1.10 2008/03/05 03:00:17 scott Exp $
* @author Chris <chris@interspire.com>
*
* @package SendStudio
* @subpackage SendStudio_Functions
*/
/**
* Include the base sendstudio functions.
*/
require_once(dirname(__FILE__) . '/sendstudio_functions.php');
/**
* Class for management of bouncing email addresses.
*
* @package SendStudio
* @subpackage SendStudio_Functions
*/
class Bounce extends SendStudio_Functions
{
/**
* Suppress Header and Footer for these actions
*
* @see Process
*
* @var Array
*/
var $SuppressHeaderFooter = array('process');
/**
* EmailsPerRefresh
* Number of emails to process per refresh.
*
* @var Int
*/
var $EmailsPerRefresh = 20;
/**
* Constructor
* Loads the language file.
*
* @see LoadLanguageFile
* @see PrintHeader
* @see PrintFooter
*
* @return Void Loads up the language file and adds 'send' as a valid popup window type.
*/
public function __construct()
{
$this->PopupWindows[] = 'processdisplay';
$this->PopupWindows[] = 'process';
$this->PopupWindows[] = 'popupbouncetest';
$this->PopupWindows[] = 'testbouncesettings';
$this->PopupWindows[] = 'help';
$this->LoadLanguageFile();
}
/**
* Process
* This works out where you are up to in the bounce process and takes the appropriate action.
* Most is passed off to other methods in this class for processing and displaying the right forms.
*
* @return Void Doesn't return anything.
*/
public function Process()
{
$action = (isset($_GET['Action'])) ? strtolower($_GET['Action']) : null;
$user = &GetUser();
$popup = (in_array($action, $this->PopupWindows)) ? true : false;
if (!in_array($action, $this->SuppressHeaderFooter)) {
$this->PrintHeader($popup);
}
$access = $user->HasAccess('Lists', 'Bounce');
if (!$access) {
$this->DenyAccess();
return;
}
// Check whether we are even capable of performing bounce processing.
if (!function_exists('imap_open')) {
$GLOBALS['Warning'] = GetLang('Bounce_No_ImapSupport_Intro');
$GLOBALS['ErrorMessage'] = $this->ParseTemplate('WarningMsg', true);
$this->ParseTemplate('Bounce_NoImapSupport');
return;
}
// Used for popupbouncetest and testbouncesettings.
$in_place = IEM::ifsetor($_GET['InPlace'], false);
switch ($action) {
case 'processfinished':
$this->printFinalReport();
break;
case 'processdisplay':
$this->ProcessBounceDisplay();
break;
case 'process':
$this->ProcessBounceEmails();
break;
default:
case 'bouncestep1':
IEM::sessionRemove('BounceWizard');
$this->bounceStep1();
break;
case 'bouncestep2':
$this->bounceStep2();
break;
case 'bouncestep2warning':
$this->bounceStep2Warning();
break;
case 'bouncestep3':
$this->bounceStep3();
break;
case 'bouncestep4':
$this->bounceStep4();
break;
case 'bouncestep5':
$this->bounceStep5();
break;
case 'popupbouncetest':
$this->popupBounceTest($in_place);
break;
case 'testbouncesettings':
$this->testBounceSettings($in_place);
break;
case 'help':
$topic = IEM::ifsetor($_GET['Topic'], false);
self::showHelp($topic);
break;
}
if (!in_array($action, $this->SuppressHeaderFooter)) {
$this->PrintFooter($popup);
}
}
/**
* bounceStep1
* Lets the user choose a set of lists (grouped by bounce server and username) to process bounces for.
*
* @return void Doesn't return anything. Prints out a list of Contact Lists.
*/
private function bounceStep1()
{
$tpl = GetTemplateSystem();
$bounce_server_map = $this->getBounceServerMap();
if (!count($bounce_server_map)) {
$tpl->Assign('message', $this->PrintSuccess('Bounce_NoLists'));
$tpl->ParseTemplate('Bounce_NoLists');
return;
}
$tpl->Assign('bounce_server_map', $bounce_server_map, true);
$tpl->ParseTemplate('BounceStep1');
}
/**
* bounceStep2
* Displays the bounce details for that list, allowing the user to fill in or modify the details.
*
* @return void Doesn't return anything. Prints out bounce details form.
*/
private function bounceStep2()
{
$user = &GetUser();
$list_api = $this->GetApi('Lists');
$bd = self::hold('TestBounceDetails');
$list = IEM::ifsetor(intval($_POST['list']), null);
if (!$list) {
$list = self::hold('list');
}
// User should have edit permissions for the list since they can change its bounce settings.
// TODO: it should be for all lists that share this bounce server and username.
$access = $user->HasAccess('lists', 'edit', $list);
// Get bounce details for list.
if (!$list_api->Load($list) || !$access) {
$GLOBAL['ErrorMessage'] = GetLang('ListDoesntExist');
$this->DenyAccess();
return;
}
// Remember the List ID obtained in step 1.
self::hold('list', $list);
// The bounce details template still uses the old system.
$GLOBALS['Bounce_Server'] = IEM::ifsetor($bd['server'], $list_api->bounceserver);
$GLOBALS['Bounce_Username'] = IEM::ifsetor($bd['username'], $list_api->bounceusername);
$GLOBALS['Bounce_Password'] = IEM::ifsetor($bd['password'], $list_api->bouncepassword);
$imap = IEM::ifsetor($bd['imap'], $list_api->imapaccount);
$GLOBALS['Imap_Selected'] = ($imap) ? ' selected="selected"' : '';
$GLOBALS['DisplayExtraMailSettings'] = 'none';
$extra_settings = IEM::ifsetor($bd['extra_settings'], $list_api->extramailsettings);
if ($extra_settings) {
$GLOBALS['DisplayExtraMailSettings'] = '';
$GLOBALS['Bounce_ExtraOption'] = ' checked="checked"';
$GLOBALS['Bounce_ExtraSettings'] = $extra_settings;
}
$GLOBALS['Bounce_AgreeDeleteAll'] = $list_api->agreedeleteall ? ' checked="checked"' : '';
$tpl = GetTemplateSystem();
// Show the manual settings if we're coming back from a future step.
$tpl->Assign('show_manual', !is_null(($bd['server'])));
$tpl->Assign('list_url', IEM::urlFor('Lists', array('Action' => 'edit', 'id' => $list)));
$tpl->Assign('list_name', $list_api->name, true);
$tpl->Assign('cron_url', IEM::urlFor('Settings', array('Tab' => 4)));
$tpl->ParseTemplate('BounceStep2');
}
/**
* bounceStep2Warning
* Displays errors from trying to login to the bounce server.
*
* @return void Doesn't return anything. Prints out error messages.
*/
private function bounceStep2Warning()
{
$errors = self::hold('ConnectionErrors');
$bd = self::hold('TestBounceDetails');
// Generate this for debugging purposes so more error handling can be added later.
$error_report = '';
foreach ($errors as $error) {
$error_report .= $error . "\n\n";
}
$real_error = self::getRealError($errors);
$tpl = GetTemplateSystem();
if (IEM::ifsetor($real_error['unknown'], false)) {
$tpl->Assign('problem_type', 'unknown');
}
$GLOBALS['Error'] = sprintf(GetLang('Bounce_Connection_Failed'), $bd['server']);
$tpl->Assign('message', $this->ParseTemplate('ErrorMsg', true, false));
$tpl->Assign('problem_name', $real_error['name']);
$tpl->Assign('problem_advice', $real_error['advice']);
$tpl->Assign('error_report', $error_report);
$tpl->ParseTemplate('BounceStep2Warning');
}
/**
* bounceStep3
* Save the working bounce details and prompt the user to start looking for bounces.
*
* @return void Doesn't return anything.
*/
private function bounceStep3()
{
$tpl = GetTemplateSystem();
$list_api = $this->GetApi('Lists');
$bd = self::hold('TestBounceDetails');
$email_count = self::hold('EmailCount');
$list = self::hold('list');
// Get bounce details for list.
if (!$list_api->Load($list)) {
$GLOBAL['ErrorMessage'] = GetLang('ListDoesntExist');
$this->DenyAccess();
return;
}
// Save settings if asked to.
if ($bd['save_settings']) {
$this->saveBounceDetails($list, $bd);
}
// We're not testing any more so change the storage key.
self::hold('BounceDetails', $bd);
// Set up state for bounce operations and report.
$operations = array(
'EmailsToDelete' => array(),
);
self::hold('BounceOperations', $operations);
$report = array(
'HardBounces' => 0,
'SoftBounces' => 0,
'EmailsIgnored' => 0,
'EmailsDeleted' => 0,
);
self::hold('BounceReport', $report);
$tpl->Assign('heading', sprintf(GetLang('Bounce_Find_Email_Step'), $list_api->bounceemail));
$message = $this->PrintSuccess('Bounce_Connection_Success', $list_api->bounceserver);
if (!$email_count) {
$message = $this->PrintSuccess('BounceLogin_Successful');
}
$tpl->Assign('message', $message);
$tpl->Assign('email_count', $email_count);
$tpl->ParseTemplate('BounceStep3');
}
/**
* bounceStep4
* Provides a summary of the bounce emails that were found and prompts the user to process them.
*
* @return void Doesn't return anything.
*/
private function bounceStep4()
{
$list_api = $this->GetApi('Lists');
$list = self::hold('list');
$report = self::hold('BounceReport');
$bd = self::hold('BounceDetails');
// Get bounce details for list.
if (!$list_api->Load($list)) {
$GLOBAL['ErrorMessage'] = GetLang('ListDoesntExist');
$this->DenyAccess();
return;
}
$tpl = GetTemplateSystem();
$tpl->Assign('heading', sprintf(GetLang('Bounce_Remove_Contact_Step'), $list_api->bounceemail));
$bounce_count = $report['HardBounces'] + $report['SoftBounces'];
$delete_count = $report['EmailsDeleted'];
if ($bd['agreedeleteall']) {
$delete_count += $report['EmailsIgnored'];
}
if ($bounce_count > 0) {
$tpl->Assign('message', $this->PrintSuccess('Bounce_Found_Summary', $bounce_count, $report['HardBounces'], $report['SoftBounces']));
} else {
$tpl->Assign('message', $this->PrintWarning('Bounce_Found_None_Summary', $delete_count));
}
$tpl->Assign('bounce_count', $bounce_count);
$tpl->Assign('delete_count', $delete_count);
// Clear reports, counts and operations for the next (real) run.
foreach ($report as $k=>$v) {
$report[$k] = 0;
}
self::hold('BounceReport', $report);
$operations = array(
'EmailsToDelete' => array(),
);
self::hold('BounceOperations', $operations);
$tpl->ParseTemplate('BounceStep4');
}
/**
* bounceStep3
* Provies a summary of the processed emails and deletes the emails marked for deletion.
*
* @return void Doesn't return anything.
*/
private function bounceStep5()
{
$bounce_api = $this->GetApi('Bounce');
$list_api = $this->GetApi('Lists');
$list = self::hold('list');
$bd = self::hold('BounceDetails');
$report = self::hold('BounceReport');
$operations = self::hold('BounceOperations');
// Get bounce details for list.
if (!$list_api->Load($list)) {
$GLOBAL['ErrorMessage'] = GetLang('ListDoesntExist');
$this->DenyAccess();
return;
}
$tpl = GetTemplateSystem();
$tpl->Assign('heading', sprintf(GetLang('Bounce_Finished_Step'), $list_api->bounceemail));
$tpl->Assign('message', $this->PrintSuccess('Bounce_Process_Success', ($report['HardBounces'] + $report['SoftBounces'])));
$tpl->ParseTemplate('BounceStep5');
flush();
// Delete the emails marked for deletion.
$bounce_api->Set('bounceserver', $bd['server']);
$bounce_api->Set('bounceuser', $bd['username']);
$bounce_api->Set('bouncepassword', base64_encode($bd['password']));
$bounce_api->Set('imapaccount', $bd['imap']);
$bounce_api->Set('extramailsettings', $bd['extra_settings']);
$inbox = $bounce_api->Login();
foreach ($operations['EmailsToDelete'] as $email_index) {
$bounce_api->DeleteEmail($email_index);
}
$bounce_api->Logout(true);
}
/**
* processBounceDisplay
* Bootstraps the manual bounce processing by loading up the initial thickbox contents.
*
* @see processBounceEmails
*
* @return void Does not return anything.
*/
private function processBounceDisplay()
{
$bd = self::hold('BounceDetails');
$email_count = self::hold('EmailCount');
$params = '&DryRun=true';
$bounce_opts = array(
'inactive_hbounce' => IEM::ifsetor($_GET['inactive_hbounce'], false),
'inactive_sbounce' => IEM::ifsetor($_GET['inactive_sbounce'], false),
'delete_hbounce' => IEM::ifsetor($_GET['delete_hbounce'], false),
);
if ($bounce_opts['inactive_hbounce'] !== false) {
// If this is set it means this is not a dry run.
$GLOBALS['ProgressTitle'] = GetLang('BounceResults_InProgress');
$GLOBALS['ProgressMessage'] = sprintf(GetLang('BounceResults_InProgress_Message'), $email_count);
$params = '';
foreach ($bounce_opts as $k=>$v) {
$params .= "&{$k}={$v}";
}
} else {
$GLOBALS['ProgressTitle'] = GetLang('Bounce_Finding_Bounces');
$GLOBALS['ProgressMessage'] = GetLang('Bounce_Attempt_To_Find');
}
$GLOBALS['ProgressReport'] = $this->getStatusReport();
$GLOBALS['ProgressStatus'] = sprintf(GetLang('BounceResults_InProgress_Progress'), 0, $this->FormatNumber($email_count));
$GLOBALS['ProgressURLAction'] = 'index.php?Page=Bounce&Action=Process' . $params;
$this->ParseTemplate('ProgressReport_Popup');
flush();
}
/**
* processBounceEmails
* Loops over a certain number of emails (determined by $this->EmailsPerRefresh) and processes them.
* It then saves where it's up to and refreshes the thickbox to do it again until finished.
*
* @see processBounceDisplay
*
* @return void Does not return anything.
*/
private function processBounceEmails()
{
$bounce_api = $this->GetApi('Bounce');
$bd = self::hold('BounceDetails');
$list = self::hold('list');
$email_count = self::hold('EmailCount');
$bounce_report = self::hold('BounceReport');
$bounce_operations = self::hold('BounceOperations');
// Determine which URL to go to on completion.
$dry_run = IEM::ifsetor($_GET['DryRun'], false);
$continue_to = 'BounceStep5';
if ($dry_run) {
$continue_to = 'BounceStep4';
}
$finish_url = 'index.php?Page=Bounce&Action=' . $continue_to;
// Get the position that we're up to for the next round of processing.
$start_position = intval(IEM::ifsetor($_GET['Email'], 1));
if ($start_position > $email_count) {
self::setJSRedirect($finish_url, 'self.parent.parent', 0);
exit();
}
$bounce_api->Set('bounceserver', $bd['server']);
$bounce_api->Set('bounceuser', $bd['username']);
$bounce_api->Set('bouncepassword', base64_encode($bd['password']));
$bounce_api->Set('imapaccount', $bd['imap']);
$bounce_api->Set('extramailsettings', $bd['extra_settings']);
// Connect to the mailbox.
$inbox = $bounce_api->Login();
if (!$inbox) {
$GLOBALS['Error'] = sprintf(GetLang('BadLogin_Details'), $bounce_api->Get('ErrorMessage'));
$msg = $this->ParseTemplate('ErrorMsg', true, false);
$msg = addcslashes(str_replace("\n", '\n', $msg), "\"'");
echo "<script src=\"includes/js/jquery.js\"></script>\n";
echo "<script>
parent.$('#ProgressReportContainer > div').hide();
parent.$('#ProgressReportMessage').html('{$msg}');
parent.$('#ProgressReportTitle').show();
parent.$('#ProgressReportMessage').show();
</script>\n";
exit();
}
// Process another chunk of emails.
for ($index=0; $index<$this->EmailsPerRefresh; $index++) {
$email_index = $start_position + $index;
// Redirect if finished.
if ($email_index > $email_count) {
// Session data changed in this loop needs to be saved before redirecting.
self::hold('EmailCount', $email_count);
self::hold('BounceOperations', $bounce_operations);
$bounce_api->Logout();
self::setJSRedirect($finish_url, 'self.parent.parent', 0);
exit();
}
// Update the progress bar status.
$report = $this->getStatusReport();
self::updateProgressReport($report);
// Caclulate the percentage completed.
$percent_processed = floor($email_index / $email_count * 100);
$message = sprintf(GetLang('BounceResults_InProgress_Progress'), $email_index, $this->FormatNumber($email_count));
self::updateProgressBar($percent_processed, $message);
// This is used to keep state for processing options.
$params = '';
// Set processing parameters.
$options = array(
'flagHardBounce' => 'inactive_hbounce',
'flagSoftBounce' => 'inactive_sbounce',
'deleteHardBounce' => 'delete_hbounce',
);
foreach ($options as $option=>$key) {
$value = IEM::ifsetor($_GET[$key], null);
if (!is_null($value)) {
$bounce_api->Set($option, (bool)$value);
$params .= "&{$key}={$value}";
}
}
// Process the actual email.
$processed = $bounce_api->ProcessEmail($email_index, $list, $dry_run);
if ($processed == 'hard') {
$bounce_operations['EmailsToDelete'][] = $email_index;
$bounce_report['HardBounces']++;
}
if ($processed == 'soft') {
$bounce_operations['EmailsToDelete'][] = $email_index;
$bounce_report['SoftBounces']++;
}
// see api/bounce.php for what 'delete' means.
if ($processed == 'delete' || ($processed == 'ignore' && $bd['agreedeleteall'])) {
$bounce_operations['EmailsToDelete'][] = $email_index;
$bounce_report['EmailsDeleted']++;
} elseif ($processed == 'ignore') {
$bounce_report['EmailsIgnored']++;
}
// We need to save this now for getStatusReport().
self::hold('BounceReport', $bounce_report);
}
$bounce_api->Logout();
self::hold('EmailCount', $email_count);
self::hold('BounceOperations', $bounce_operations);
// Need to increment email_index because emails actually start counting at 1 not 0.
// Otherwise every $this->EmailsPerRefresh emails would be processed twice.
// For example: email '20' would be processed twice - which stuffs up reporting.
$rand = rand(1, 50); // Avoid cache problems.
$next_index = $email_index + 1;
$params .= $dry_run ? '&DryRun=true': '';
self::setJSRedirect("index.php?Page=Bounce&Action=Process{$params}&Email={$next_index}&bx={$rand}");
exit();
}
/**
* getStatusReport
* Grabs the bounce report information from the session.
*
* @return string The report in HTML.
*/
private function getStatusReport()
{
$bounce_report = self::hold('BounceReport');
$report = '';
foreach (array('HardBounces', 'SoftBounces', 'EmailsIgnored', 'EmailsDeleted') as $pos=>$key) {
$amount = $bounce_report[$key];
if ($amount == 1) {
$report .= GetLang('BounceResults_InProgress_' . $key . '_One');
} else {
$report .= sprintf(GetLang('BounceResults_InProgress_' . $key . '_Many'), $this->FormatNumber($amount));
}
$report .= '<br/>';
}
return $report;
}
/**
* hold
* Remembers bounce-related information in the session.
*
* @param string $key The key of the value to remember.
* @param mixed $value If null, will cause the function to return the value of the key, otherwise this is the value to be stored.
*
* @return mixed False if $value is null and $key has not been set, otherwise returns the value referenced by $key.
*/
private static function hold($key, $value=null)
{
$hold = IEM::sessionGet('BounceWizard');
if (!is_array($hold)) {
$hold = array();
}
if (!is_null($value)) {
$hold[$key] = $value;
IEM::sessionSet('BounceWizard', $hold);
} elseif (!isset($hold[$key])) {
// If a non-existent key is trying to be retrieved, return false.
return false;
}
return $hold[$key];
}
/**
* popupBounceTest
* Bootstraps the thickbox status window for checking bounce login details.
*
* @param boolean $in_place Whether the popup is mean to give results on this page (true) ore redirect (false).
*
* @return void Does not return anything.
*/
private function popupBounceTest($in_place = false)
{
$bounce_details = array (
'server' => $_GET['bounce_server'],
'username' => $_GET['bounce_username'],
'password' => $_GET['bounce_password'],
'extra_settings' => $_GET['bounce_extrasettings'],
'imap' => IEM::ifsetor($_GET['bounce_imap'], false),
'agreedeleteall' => IEM::ifsetor($_GET['bounce_agreedeleteall'], false),
'save_settings' => IEM::ifsetor($_GET['savebounceserverdetails'], false),
);
// Decrypt the password, as it was encrypted with a JavaScript XOR routine to send here.
$bounce_details['password'] = IEM::decrypt($bounce_details['password'], IEM::sessionGet('RandomToken'));
self::hold('TestBounceDetails', $bounce_details);
$GLOBALS['ProgressTitle'] = GetLang('Bounce_Connecting');
$GLOBALS['ProgressMessage'] = GetLang('Bounce_Connecting_Msg');
$GLOBALS['ProgressReport'] = ''.
$GLOBALS['ProgressStatus'] = '';
$GLOBALS['ProgressURLAction'] = 'index.php?Page=Bounce&Action=TestBounceSettings';
if ($in_place) {
$GLOBALS['ProgressURLAction'] .= '&InPlace=true';
}
$this->ParseTemplate('ProgressReport_Popup');
}
/**
* testCombination
* Attempts to login to a boucne mailbox.
*
* @param array $bd The bounce details to connect with, except for the extra settings.
* @param string $extra_settings The extra settings to connect with, e.g. /ssl/tls.
*
* @return array A Boolean value indiciating success or failure, and a second element containing either the number of emails in the inbox (on success) or the error message (on failure).
*/
private static function testCombination($bd, $extra_settings)
{
$bounce_api = self::GetApi('Bounce');
$bounce_api->Set('bounceserver', $bd['server']);
$bounce_api->Set('bounceuser', $bd['username']);
$bounce_api->Set('bouncepassword', base64_encode($bd['password']));
$bounce_api->Set('imapaccount', $bd['imap']);
$bounce_api->Set('extramailsettings', $extra_settings);
$login_ok = $bounce_api->Login();
if (!$login_ok) {
return array(false, $bounce_api->ErrorMessage);
}
$count = $bounce_api->GetEmailCount();
$bounce_api->Logout(false);
return array(true, $count);
}
/**
* handle
* Perform the appropriate action after the Bounce Test popup has completed.
* For manual bounce processing, this will redirect to the next step.
* For other cases it will either report success and close or display the error.
*
* @param string $type One of 'next_combo', 'error_report' or 'success'.
* @param boolean $in_place Whether the action should take place in the popup (true) or redirect.
*
* @return void Does not return anything. It prints JavaScript (ugh!).
*/
private static function handle($type, $in_place)
{
$bd = self::hold('TestBounceDetails');
$root = 'index.php?Page=Bounce&Action=';
$urls = array(
'next_combo' => array('TestBounceSettings', 'window', 150),
'error_report' => array('BounceStep2Warning', 'self.parent.parent', 0),
'success' => array('BounceStep3', 'self.parent.parent', 0)
);
if (!$in_place || $type == 'next_combo') {
$url = $root . $urls[$type][0];
$url .= $in_place ? '&InPlace=true' : '';
self::setJSRedirect($url, $urls[$type][1], $urls[$type][2]);
exit();
}
// Replace the progress bar with a message.
// TODO: work out some nicer way to do this.
echo "<script src=\"includes/js/jquery.js\"></script>\n";
if ($type == 'error_report') {
// Alert the appropriate error.
$error = self::getRealError(self::hold('ConnectionErrors'));
$tpl = GetTemplateSystem();
$GLOBALS['Error'] = "<strong>" . $error['name'] . "</strong>";
$GLOBALS['Error'] .= "<ul style=\"padding-left:2em;\">";
foreach ($error['advice'] as $title => $article) {
$GLOBALS['Error'] .= "<li><a href=\"javascript:LaunchHelp('".IEM::enableInfoTipsGet()."',{$article});\">{$title}</a></li>\n";
}
$GLOBALS['Error'] .= "</ul>";
$msg = $tpl->ParseTemplate('ErrorMsg', true);
} elseif ($type == 'success') {
// Set the combo in the UI and disappear.
$msg = self::PrintSuccess('BounceLogin_Successful');
}
$msg = str_replace("\n", '\n', addslashes($msg));
echo "<script>
parent.$('#ProgressReportContainer > div').hide();
parent.$('#ProgressReportMessage').html('{$msg}');
parent.$('#ProgressReportTitle').show();
parent.$('#ProgressReportMessage').show();\n";
if ($type == 'success') {
echo "self.parent.parent.$('#bounce_extrasettings').val('{$bd['extra_settings']}');\n";
echo "self.parent.parent.Application.Page.BounceInfo.evaluateCheckboxes();\n";
echo "setTimeout(function() { self.parent.parent.tb_remove(); }, 1500);\n";
} else {
echo "parent.$('#ProgressReportWindow_Close').show();\n";
}
echo "</script>\n";
exit();
}
/**
* testBounceSettings
* Produces the contents of the thickbox used when checking bounce login details.
* If specified, it will try all possible combinations of extra settings to get a connection.
*
* @param boolean $in_place If set to true, will update the extra settings in place and not redirect.
*
* @return void Does not return anything.
*/
private function testBounceSettings($in_place = false)
{
$tpl = GetTemplateSystem();
$bd = self::hold('TestBounceDetails');
$upto_combo = IEM::ifsetor($bd['upto_combo'], 0);
$combinations = array($bd['extra_settings']);
// If extra settings aren't specified, we need to auto-detect.
if (!$bd['extra_settings']) {
$combinations = $this->generateConnectionCombinations();
}
if ($upto_combo > count($combinations)) {
// Handle the case where checking has finished but no solution has been found.
self::handle('error_report', $in_place);
}
if ($upto_combo == 0) {
// Reset error log.
self::hold('ConnectionErrors', array());
// Check the sever can actually be connected to (manually, so we can customise the timeout).
$message = sprintf(GetLang('Bounce_Connecting_To'), $bd['server']);
self::updateProgressBar(0, $message);
list($success, $error) = self::testConnection($bd);
if (!$success) {
$error_log[] = $error;
self::hold('ConnectionErrors', $error_log);
self::handle('error_report', $in_place);
}
}
// Update progress window status.
$percent_processed = floor($upto_combo / count($combinations) * 100);
self::updateProgressBar($percent_processed);
// Attempt a login with one of the settings combinations.
list($success, $count_or_error) = self::testCombination($bd, $combinations[$upto_combo]);
if ($success) {
// Store the email count for the next step.
self::hold('EmailCount', $count_or_error);
// Save the successfull extra settings.
$bd['extra_settings'] = $combinations[$upto_combo];
self::hold('TestBounceDetails', $bd);
// Redirect to the next step.
self::updateProgressBar(100);
self::handle('success', $in_place);
}
// Combination failed - record error and try the next combination.
$error_message = $combinations[$upto_combo] . ': ' . $count_or_error;
$error_log[] = $error_message;
self::hold('ConnectionErrors', $error_log);
$error = self::getRealError($error_message);
if ($error['fatal'] || count($combinations) == 1) {
// No point continuing to try after a fatal error.
self::updateProgressBar(100);
self::handle('error_report', $in_place);
}
$bd['upto_combo']++;
self::hold('TestBounceDetails', $bd);
self::handle('next_combo', $in_place);
}
/**
* testConnection
* Connects to the bounce server using fsockopen with a specified timeout.
*
* @param array Bounce server details [(string)server, (bool)imap, ...].
* @param int $timeout Defaults to 10 seconds.
*
* @return array Returns an array of the form [(bool)status, (string)error_message].
*/
private static function testConnection($bounce_details, $timeout = 10)
{
$server = $bounce_details['server'];
$port = false;
if (strpos($server, ':') !== false) {
list($server, $port) = explode(':', $server);
}
if (!$port) {
$port = $bounce_details['imap'] ? 143 : 110;
}
$fp = @fsockopen($server, $port, $errno, $errstr, $timeout);
if (!$fp) {
return array(false, $errstr);
}
return array(true, '');
}
/**
* generateConnectionCombinations
* Generates possible connection strings to pass to imap_open.
*
* @return array An array of connection strings, e.g. ['/ssl/tls', '/ssl/notls', ...].
*/
private function generateConnectionCombinations()
{
$ssl = array('', '/ssl', '/nossl');
$tls = array('', '/tls', '/notls');
$cert = array('', '/novalidate-cert');
$combinations = array();
foreach ($ssl as $use_ssl) {
foreach ($tls as $use_tls) {
foreach ($cert as $cert_check) {
$combinations[] = "{$use_ssl}{$use_tls}{$cert_check}";
}
}
}
return array_unique($combinations);
}
/**
* getBounceServerMap
* Obtains a list of Contact Lists, grouped by bounce details (server and username).
*
* @return array The list of contact lists, grouped by server and username.
*/
private function getBounceServerMap()
{
$user = &GetUser();
$lists = $user->GetLists();
$bounce_server_map = array();
// Contact lists are grouped by their bounce server and bounce username.
foreach ($lists as $list) {
$key = $list['bounceserver'] . $list['bounceusername'];
if (!$list['bounceserver'] || !$list['bounceusername']) {
// Prefix keys of lists we do not group with a ~ to place them at the end.
$key = '~' . $list['name'];
}
if (!isset($bounce_server_map[$key])) {
$bounce_server_map[$key] = array();
}
$bounce_server_map[$key][] = array(
'id' => $list['listid'],
'name' => $list['name'],
'server' => $list['bounceserver'],
'username' => $list['bounceusername'],
);
}
ksort($bounce_server_map);
return $bounce_server_map;
}
/**
* saveBounceDetails
* Saves the bounce details for all lists that share the same bounce server and username.
*
* @param int $lid The list ID to use to locate the other lists.
* @param array $details The bounce details to set for these lists.
*
* @return boolean True for success, otherwise false.
*/
private function saveBounceDetails($lid, $details)
{
$list_api = $this->GetApi('Lists');
$map = $this->getBounceServerMap();
$group = null;
// Check the map for the other lists like the one passed in.
foreach ($map as $k=>$lists) {
foreach ($lists as $list) {
if ($list['id'] == $lid) {
$group = $map[$k];
}
}
}
if (!is_array($group)) {
return false;
}
// Save the details for each list.
foreach ($group as $list) {
if (!$list_api->Load($list['id'])) {
trigger_error("Unable to load list with ID {$list['id']}.", E_USER_NOTICE);
continue;
}
$list_api->Set('bounceserver', $details['server']);
$list_api->Set('bounceusername', $details['username']);
$list_api->Set('bouncepassword', $details['password']);
$list_api->Set('imapaccount', $details['imap']);
$list_api->Set('extramailsettings', $details['extra_settings']);
$list_api->Set('processbounce', true);
$list_api->Set('agreedelete', true);
$list_api->Set('agreedeleteall', $details['agreedeleteall']);
$list_api->Save();
}
return true;
}
/**
* updateProgressBar
* Updates the percentage of the progress bar for the progress thickbox.
*
* @param int $percentage The percentage the progress bar should reflect.
* @param string $message A message underneath the progress bar.
*
* @return void Does not return anything. Flushes output.
*/
private static function updateProgressBar($percentage, $message = '')
{
$message = str_replace("'", "\'", $message);
echo "<script defer=\"defer\">\n";
echo sprintf("self.parent.UpdateStatus('%s', %d);\n", $message, $percentage);
echo "</script>\n";
flush();
}
/**
* updateProgressReport
* Updates the report in the progress thickbox.
*
* @param string $report The report to put in the progress thickbox.
*
* @return void Does not return anything. Flushes output.
*/
private static function updateProgressReport($report)
{
echo "<script>\n";
echo sprintf("self.parent.UpdateStatusReport('%s');\n", addcslashes($report, "'\";/\\\0..\37!@\177..\377"));
echo "</script>\n";
flush();
}
/**
* setJSRedirect
* Makes a setTimeout JS call to the given URL.
*
* @param string $url The URL to redirect to.
* @param string $target The target window to perform the redirect in.
* @param int $delay The number of milliseconds to wait before redirecting.
*
* @return void Does not return anything. Flushes output.
*/
private static function setJSRedirect($url, $target = 'window', $delay = 150)
{
$delay = intval($delay);
$url = str_replace("'", "\'", $url);
echo "<script>\n";
echo "setTimeout(function() { {$target}.location = '{$url}'; }, {$delay});\n";
echo "</script>\n";
flush();
}
/**
* getRealError
* Takes a list of errors from multiple bounce server login attempts and returns a generic version of the most appropriate error message.
*
* @param array|string $error_list List of error messages from bounce server login attempts.
*
* @return array A generic version of the most appropriate error in the list in the form [pattern, name, advice, fatal].
*/
private static function getRealError($error_list)
{
if (!is_array($error_list)) {
$error_list = array($error_list);
}
// The earlier the error is defined, the higher priority it has.
// An error that is fatal is one that no amount of auto-detection can get around (e.g. bad login details).
$possible_errors = array(
'bad_host' => array(
'pattern' => array('/No such host/i', '/Name or service not known/i'),
'name' => GetLang('Bounce_Error_InvalidServer'),
'advice' => array('Bounce_Help_InvalidServer1' => 862, 'Bounce_Help_TimeOut1' => 863),
'fatal' => true,
),
'timeout' => array(
'pattern' => '/ time/i',
'name' => GetLang('Bounce_Error_TimeOut'),
'advice' => array('Bounce_Help_TimeOut1' => 863, 'Bounce_Help_TimeOut2' => 864),
'fatal' => true,
),
'connection_refused' => array(
'pattern' => '/Connection refused/i',
'name' => GetLang('Bounce_Error_ConnRefused'),
'advice' => array('Bounce_Help_TimeOut2' => 864, 'Bounce_Help_TimeOut1' => 863),
'fatal' => true,
),
'bad_login' => array(
'pattern' => array('/Login failed/i', '/Authentication failed/i', '/Logon failure/i'),
'name' => GetLang('Bounce_Error_LoginFailed'),
'advice' => array('Bounce_Help_LoginFailed1' => 865),
'fatal' => true,
),
// The following errors should be overcome if using auto-detection.
'cert_failure' => array(
'pattern' => '/Certificate failure for/i',
'name' => GetLang('Bounce_Error_CertFailure'),
'advice' => array('Bounce_Help_CertFailure1' => 860),
'fatal' => false,
),
'ssl_neg_failed' => array(
'pattern' => array('/SSL negotiation failed/i', '/Unable to negotiate TLS with this server/i'),
'name' => GetLang('Bounce_Error_SSLFailed'),
'advice' => array('Bounce_Help_SSLFailed1' => 866),
'fatal' => false,
),
'invalid_spec' => array(
'pattern' => '/invalid remote specification/i',
'name' => GetLang('Bounce_Error_InvalidSpec'),
'advice' => array('Bounce_Help_TimeOut2' => 864, 'Bounce_Help_InvalidSpec1' => 867),
'fatal' => false,
),
'unknown' => array(
'pattern' => '/./',
'name' => GetLang('Bounce_Error_Unknown'),
'advice' => array('Bounce_Help_TimeOut1' => 863, 'Bounce_Help_TimeOut2' => 864),
'fatal' => false,
'unknown' => true,
),
);
$actual_error = $possible_errors['unknown'];
foreach ($possible_errors as $k=>$p) {
foreach ($error_list as $error) {
if (!is_array($p['pattern'])) {
$p['pattern'] = array($p['pattern']);
}
foreach ($p['pattern'] as $pattern) {
if (preg_match($pattern, $error)) {
$actual_error = $p;
break 3;
}
}
}
}
// Fill in the language variables for the advice.
$advice = array();
foreach ($actual_error['advice'] as $tok=>$article) {
$lang = GetLang($tok);
$advice[$lang] = $article;
}
$actual_error['advice'] = $advice;
return $actual_error;
}
/**
* showHelp
* Displays a help message, to be placed within a thickbox window.
*
* @param string $topic The topics to display.
*
* @return boolean Returns false on error, otherwise true.
*/
private static function showHelp($topic)
{
$questions = array(
'list_group' => 'WhyListsGrouped',
'auto_explain' => 'Bounce_Auto_Process_Why',
'manual_explain' => 'Bounce_Manual_Process_Why',
);
$answers = array(
'list_group' => 'Bounce_Why_Group_Lists',
'auto_explain' => 'Bounce_Why_Use_Auto',
'manual_explain' => 'Bounce_Why_Not_Manual',
);
if (!array_key_exists($topic, $questions)) {
return false;
}
$tpl = GetTemplateSystem();
$tpl->Assign('heading', GetLang($questions[$topic], true));
$tpl->Assign('message', GetLang($answers[$topic], true));
$tpl->ParseTemplate('Help_PopUp');
return true;
}
}