1<?php 2 3require_once dirname(__FILE__).'/accesscheck.php'; 4 5if (!$GLOBALS['commandline'] && !$GLOBALS['inRemoteCall']) { 6 // browser session 7 ob_end_flush(); 8 if (!MANUALLY_PROCESS_BOUNCES) { 9 echo $GLOBALS['I18N']->get('This page can only be called from the commandline'); 10 11 return; 12 } 13 if (isset($_GET['login']) || isset($_GET['password'])) { 14 echo Error(s('Remote processing of the queue is now handled with a processing secret')); 15 16 return; 17 } 18 19 //# we're in a normal session, so the csrf token should work 20 verifyCsrfGetToken(); 21} 22 23flush(); 24$outputdone = 0; 25function prepareOutput() 26{ 27 global $outputdone; 28 if (!$outputdone) { 29 $outputdone = 1; 30 31 return formStart('name="outputform" class="processbounces" ').'<textarea name="output" rows=10 cols=50></textarea></form>'; 32 } 33} 34 35$report = ''; 36//# some general functions 37function finishBounceProcessing($flag, $message) 38{ 39 if ($flag == 'error') { 40 $subject = $GLOBALS['I18N']->get('Bounce processing error'); 41 } elseif ($flag == 'info') { 42 $subject = $GLOBALS['I18N']->get('Bounce Processing info'); 43 } 44 if (!TEST && $message) { 45 sendReport($subject, $message); 46 } 47} 48 49function bounceProcessError($message) 50{ 51 outputProcessBounce("$message"); 52 finishBounceProcessing('error', $message); 53 exit; 54} 55 56function processbounces_shutdown() 57{ 58 global $report, $process_id; 59 releaseLock($process_id); 60 // $report .= "Connection status:".connection_status(); 61 finishBounceProcessing('info', $report); 62} 63 64function outputProcessBounce($message, $reset = 0) 65{ 66 $infostring = '['.date('D j M Y H:i', 67 time()).'] ['.getenv('REMOTE_HOST').'] ['.getClientIP().']'; 68 //print "$infostring $message<br/>\n"; 69 $message = preg_replace("/\n/", '', $message); 70 //# contribution from http://forums.phplist.com/viewtopic.php?p=14648 71 //# in languages with accented characters replace the HTML back 72 //Replace the "’" which is not replaced by html_decode 73 $message = preg_replace('/’/', "'", $message); 74 //Decode HTML chars 75 //$message = html_entity_decode($message,ENT_QUOTES,$_SESSION['adminlanguage']['charset']); 76 $message = html_entity_decode($message, ENT_QUOTES, 'UTF-8'); 77 if ($GLOBALS['commandline']) { 78 cl_output($message); 79 } elseif ($GLOBALS['inRemoteCall']) { 80 ob_end_clean(); 81 echo $message, "\n"; 82 ob_start(); 83 } else { 84 if ($reset) { 85 echo '<script language="Javascript" type="text/javascript"> 86// if (document.forms[0].name == "outputform") { 87 document.outputform.output.value = ""; 88 document.outputform.output.value += "\n"; 89// } 90 </script>' .PHP_EOL; 91 } 92 93 echo '<script language="Javascript" type="text/javascript"> 94// if (document.forms[0].name == "outputform") { 95 document.outputform.output.value += "' .$message.'"; 96 document.outputform.output.value += "\n"; 97// } else 98// document.writeln("' .$message.'"); 99 </script>' .PHP_EOL; 100 } 101 102 flush(); 103} 104 105function findMessageId($text) 106{ 107 $msgid = 0; 108 109 if (preg_match('/(?:X-MessageId|X-Message): (.*)\r\n/iU', $text, $match)) { 110 $msgid = trim($match[1]); 111 } 112 113 return $msgid; 114} 115 116function findUserID($text) 117{ 118 global $tables; 119 $userid = 0; 120 121 if (preg_match('/(?:X-ListMember|X-User): (.*)\r\n/iU', $text, $match)) { 122 $user = trim($match[1]); 123 124 // some versions used the email to identify the users, some the userid and others the uniqid 125 // use backward compatible way to find user 126 if (strpos($user, '@') !== false) { 127 $userid_req = Sql_Fetch_Row_Query(sprintf('select id from %s where email = "%s"', $tables['user'], 128 sql_escape($user))); 129 if ($userid_req) { 130 $userid = $userid_req[0]; 131 } 132 } elseif (preg_match("/^\d$/", $user)) { 133 $userid = $user; 134 } elseif (!empty($user)) { 135 $userid_req = Sql_Fetch_Row_Query(sprintf('select id from %s where uniqid = "%s"', $tables['user'], 136 sql_escape($user))); 137 if ($userid_req) { 138 $userid = $userid_req[0]; 139 } 140 } 141 } else { 142 //## if we didn't find any, parse anything looking like an email address and check if it's a subscriber. 143 //# this is probably fairly time consuming, but as the process is only done once every so often 144 //# that should not be too bad 145 146 preg_match_all('/[._a-zA-Z0-9-]+@[.a-zA-Z0-9-]+/', $text, $regs); 147 148 foreach ($regs[0] as $email) { 149 $useridQ = Sql_Fetch_Row_Query(sprintf('select id from %s where email = "%s"', $tables['user'], 150 sql_escape($email))); 151 if (!empty($useridQ[0])) { 152 $userid = $useridQ[0]; 153 break; 154 } 155 } 156 } 157 158 return $userid; 159} 160 161function decodeBody($header, $body) 162{ 163 $transfer_encoding = ''; 164 if (preg_match('/Content-Transfer-Encoding: ([\w-]+)/i', $header, $regs)) { 165 $transfer_encoding = strtolower($regs[1]); 166 } 167 switch ($transfer_encoding) { 168 case 'quoted-printable': 169 $decoded_body = @imap_qprint($body); 170 break; 171 case 'base64': 172 $decoded_body = @imap_base64($body); 173 break; 174 case '7bit': 175 case '8bit': 176 default: 177 // $body = $body; 178 } 179 if (!empty($decoded_body)) { 180 return $decoded_body; 181 } else { 182 return $body; 183 } 184} 185 186function processImapBounce($link, $num, $header) 187{ 188 global $tables; 189 $headerinfo = imap_headerinfo($link, $num); 190 $bounceDate = @strtotime($headerinfo->date); 191 $body = imap_body($link, $num); 192 $body = decodeBody($header, $body); 193 194 $msgid = findMessageId($body); 195 $userid = findUserID($body); 196 if (VERBOSE) { 197 outputProcessBounce('UID'.$userid.' MSGID'.$msgid); 198 } 199 200 //# @TODO add call to plugins to determine what to do. 201 // for now, quick hack to zap MsExchange Delayed messages 202 if (preg_match('/Action: delayed\s+Status: 4\.4\.7/im', $body)) { 203 //# just say we did something, when actually we didn't 204 return true; 205 } 206 $bounceDateFormatted = date('Y-m-d H:i:s', $bounceDate); 207 208 Sql_Query(sprintf('insert into %s (date,header,data) 209 values("%s","%s","%s")', 210 $tables['bounce'], 211 $bounceDateFormatted, 212 addslashes($header), 213 addslashes($body))); 214 215 $bounceid = Sql_Insert_Id(); 216 217 return processBounceData($bounceid, $msgid, $userid, $bounceDateFormatted); 218} 219 220function processBounceData($bounceid, $msgid, $userid, $bounceDate = null) 221{ 222 global $tables; 223 $useremailQ = Sql_fetch_row_query(sprintf('select email from %s where id = %d', $tables['user'], $userid)); 224 if (empty($useremailQ)) { 225 $useremail = ""; 226 } else { 227 $useremail = $useremailQ[0]; 228 } 229 230 if ($bounceDate === null) { 231 $bounceDate = date('Y-m-d H:i', time()); 232 } 233 234 if ($msgid === 'systemmessage' && !empty($userid)) { 235 Sql_Query(sprintf('update %s 236 set status = "bounced system message", 237 comment = "%s marked unconfirmed" 238 where id = %d', 239 $tables['bounce'], 240 $userid, $bounceid)); 241 242 #Use the date of the bounce, instead of "now" as processing may be different 243 Sql_Query(sprintf('INSERT INTO %s 244 ( 245 user, 246 message, 247 bounce, 248 time 249 ) 250 VALUES 251 ( 252 %d, 253 -1, 254 %d, 255 "%s" 256 )', 257 $tables['user_message_bounce'], 258 $userid, $bounceid, $bounceDate) 259 ); 260 logEvent("$userid ".$GLOBALS['I18N']->get('system message bounced, user marked unconfirmed')); 261 addUserHistory($useremail, $GLOBALS['I18N']->get('Bounced system message'), ' 262 <br/>' .$GLOBALS['I18N']->get('User marked unconfirmed')." 263 <br/><a href=\"./?page=bounce&id=$bounceid\">".$GLOBALS['I18N']->get('View Bounce').'</a> 264 265 '); 266 Sql_Query(sprintf('update %s 267 set confirmed = 0 268 where id = %d', 269 $tables['user'], 270 $userid)); 271 } elseif (!empty($msgid) && !empty($userid)) { 272 //# check if we already have this um as a bounce 273 //# so that we don't double count "delayed" like bounces 274 $exists = Sql_Fetch_Row_Query(sprintf('select count(*) from %s where user = %d and message = %d', 275 $tables['user_message_bounce'], $userid, $msgid)); 276 if (empty($exists[0])) { 277 Sql_Query(sprintf('insert into %s 278 set user = %d, message = %d, bounce = %d', 279 $tables['user_message_bounce'], 280 $userid, $msgid, $bounceid)); 281 Sql_Query(sprintf('update %s 282 set status = "bounced list message %d", 283 comment = "%s bouncecount increased" 284 where id = %d', 285 $tables['bounce'], 286 $msgid, 287 $userid, $bounceid)); 288 Sql_Query(sprintf('update %s 289 set bouncecount = bouncecount + 1 290 where id = %d', 291 $tables['message'], 292 $msgid)); 293 Sql_Query(sprintf('update %s 294 set bouncecount = bouncecount + 1 295 where id = %d', 296 $tables['user'], 297 $userid)); 298 } else { 299 //# we create the relationship, but don't increase counters 300 Sql_Query(sprintf('insert into %s 301 set user = %d, message = %d, bounce = %d', 302 $tables['user_message_bounce'], 303 $userid, $msgid, $bounceid)); 304 305 //# we cannot translate this text 306 Sql_Query(sprintf('update %s 307 set status = "duplicate bounce for %d", 308 comment = "duplicate bounce for subscriber %d on message %d" 309 where id = %d', 310 $tables['bounce'], 311 $userid, 312 $userid, $msgid, $bounceid)); 313 } 314 } elseif ($userid) { 315 Sql_Query(sprintf('update %s 316 set status = "bounced unidentified message", 317 comment = "%s bouncecount increased" 318 where id = %d', 319 $tables['bounce'], 320 $userid, $bounceid)); 321 Sql_Query(sprintf('update %s 322 set bouncecount = bouncecount + 1 323 where id = %d', 324 $tables['user'], 325 $userid)); 326 } elseif ($msgid === 'systemmessage') { 327 Sql_Query(sprintf('update %s 328 set status = "bounced system message", 329 comment = "unknown user" 330 where id = %d', 331 $tables['bounce'], 332 $bounceid)); 333 logEvent("$userid ".$GLOBALS['I18N']->get('system message bounced, but unknown user')); 334 } elseif ($msgid) { 335 Sql_Query(sprintf('update %s 336 set status = "bounced list message %d", 337 comment = "unknown user" 338 where id = %d', 339 $tables['bounce'], 340 $msgid, 341 $bounceid)); 342 Sql_Query(sprintf('update %s 343 set bouncecount = bouncecount + 1 344 where id = %d', 345 $tables['message'], 346 $msgid)); 347 } else { 348 Sql_Query(sprintf('update %s 349 set status = "unidentified bounce", 350 comment = "not processed" 351 where id = %d', 352 $tables['bounce'], 353 $bounceid)); 354 355 return false; 356 } 357 358 return true; 359} 360 361function processPop($server, $user, $password) 362{ 363 $port = $GLOBALS['bounce_mailbox_port']; 364 if (!$port) { 365 $port = '110/pop3/notls'; 366 } 367 set_time_limit(6000); 368 369 $link = imap_open('{'.$server.':'.$port.'}INBOX', $user, $password); 370 371 if (!$link) { 372 outputProcessBounce($GLOBALS['I18N']->get('Cannot create POP3 connection to')." $server: ".imap_last_error()); 373 374 return false; 375 } 376 377 return processMessages($link, 100000); 378} 379 380function processMbox($file) 381{ 382 set_time_limit(6000); 383 384 if (!TEST) { 385 $link = imap_open($file, '', '', CL_EXPUNGE); 386 } else { 387 $link = imap_open($file, '', ''); 388 } 389 if (!$link) { 390 outputProcessBounce($GLOBALS['I18N']->get('Cannot open mailbox file').' '.imap_last_error()); 391 392 return false; 393 } 394 395 return processMessages($link, 100000); 396} 397 398function processMessages($link, $max = 3000) 399{ 400 global $bounce_mailbox_purge_unprocessed, $bounce_mailbox_purge; 401 $num = imap_num_msg($link); 402 outputProcessBounce(s('%d bounces to fetch from the mailbox', $num).PHP_EOL); 403 404 if ($num == 0) { 405 imap_close($link); 406 407 return ''; 408 } 409 outputProcessBounce($GLOBALS['I18N']->get('Please do not interrupt this process').PHP_EOL); 410 $report = ' '.s('%d bounces to process', $num).PHP_EOL; 411 if ($num > $max) { 412 outputProcessBounce(s('Processing first %d bounces', $max).PHP_EOL); 413 $report .= s('Processing first %d bounces', $max).PHP_EOL; 414 $num = $max; 415 } 416 if (TEST) { 417 echo s('Running in test mode, not deleting messages from mailbox').'<br/>'; 418 } else { 419 echo s('Processed messages will be deleted from the mailbox').'<br/>'; 420 } 421 $nberror = 0; 422// for ($x=1;$x<150;$x++) { 423 for ($x = 1; $x <= $num; ++$x) { 424 set_time_limit(60); 425 $header = imap_fetchheader($link, $x); 426 if ($x % 25 == 0) { 427 // outputProcessBounce( $x . " ". nl2br($header)); 428 outputProcessBounce($x.' done', 1); 429 } 430 echo PHP_EOL; 431 flush(); 432 $processed = processImapBounce($link, $x, $header); 433 if ($processed) { 434 if (!TEST && $bounce_mailbox_purge) { 435 if (VERBOSE) { 436 outputProcessBounce($GLOBALS['I18N']->get('Deleting message')." $x"); 437 } 438 imap_delete($link, $x); 439 } elseif (VERBOSE) { 440 outputProcessBounce(s('Not deleting processed message')." $x $bounce_mailbox_purge"); 441 } 442 } else { 443 if (!TEST && $bounce_mailbox_purge_unprocessed) { 444 if (VERBOSE) { 445 outputProcessBounce($GLOBALS['I18N']->get('Deleting message')." $x"); 446 } 447 imap_delete($link, $x); 448 } elseif (VERBOSE) { 449 outputProcessBounce(s('Not deleting unprocessed message')." $x"); 450 } 451 } 452 flush(); 453 } 454 flush(); 455 outputProcessBounce(s('Closing mailbox, and purging messages')); 456 set_time_limit(60 * $num); 457 if (!TEST) { 458 imap_close($link, CL_EXPUNGE); 459 } else { 460 imap_close($link); 461 } 462 463 return $report; 464} 465 466if (!function_exists('imap_open')) { 467 Error($GLOBALS['I18N']->get('IMAP is not included in your PHP installation, cannot continue'). 468 '<br/>'.$GLOBALS['I18N']->get('Check out'). 469 ' <a href="http://www.php.net/manual/en/ref.imap.php">http://www.php.net/manual/en/ref.imap.php</a>'); 470 471 return; 472} 473 474if (empty($bounce_mailbox) && (empty($bounce_mailbox_host) || empty($bounce_mailbox_user) || empty($bounce_mailbox_password))) { 475 Error($GLOBALS['I18N']->get('Bounce mechanism not properly configured')); 476 477 return; 478} 479 480// lets not do this unless we do some locking first 481register_shutdown_function('processbounces_shutdown'); 482$abort = ignore_user_abort(1); 483if (!empty($GLOBALS['commandline']) && isset($cline['f'])) { 484 // force set, so kill other processes 485 cl_output(s('Force set, killing other send processes')); 486 $process_id = getPageLock(1); 487} else { 488 $process_id = getPageLock(); 489} 490if (empty($process_id)) { 491 return; 492} 493echo prepareOutput(); 494flushBrowser(); 495 496$download_report = ''; 497switch ($bounce_protocol) { 498 case 'pop': 499 $download_report = processPop($bounce_mailbox_host, $bounce_mailbox_user, $bounce_mailbox_password); 500 break; 501 case 'mbox': 502 $download_report = processMbox($bounce_mailbox); 503 break; 504 default: 505 Error($GLOBALS['I18N']->get('bounce_protocol not supported')); 506 507 return; 508} 509 510if ($GLOBALS['commandline'] && $download_report === false) { 511 cl_output(s('Download failed, exiting')); 512 513 return; 514} 515// now we have filled database with all available bounces 516 517//# reprocess the unidentified ones, as the bounce detection has improved, so it might catch more 518 519cl_output('reprocessing'); 520$reparsed = $count = 0; 521$reidentified = 0; 522$req = Sql_Query(sprintf('select * from %s where status = "unidentified bounce"', $tables['bounce'])); 523$total = Sql_Affected_Rows(); 524cl_output(s('%d bounces to reprocess', $total)); 525while ($bounce = Sql_Fetch_Assoc($req)) { 526 ++$count; 527 if ($count % 25 == 0) { 528 cl_progress(s('%d out of %d processed', $count, $total)); 529 } 530 $bounceBody = decodeBody($bounce['header'], $bounce['data']); 531 $userid = findUserID($bounceBody); 532 $messageid = findMessageId($bounceBody); 533 if (!empty($userid) || !empty($messageid)) { 534 ++$reparsed; 535 if (processBounceData($bounce['id'], $messageid, $userid)) { 536 ++$reidentified; 537 } 538 } 539} 540cl_output(s('%d out of %d processed', $count, $total)); 541if (VERBOSE) { 542 outputProcessBounce(s('%d bounces were re-processed and %d bounces were re-identified', $reparsed, $reidentified)); 543} 544$advanced_report = ''; 545$bouncerules = loadBounceRules(); 546if (count($bouncerules)) { 547 outputProcessBounce($GLOBALS['I18N']->get('Processing bounces based on active bounce rules')); 548 $matched = 0; 549 $notmatched = 0; 550 $limit = ' limit 10000'; 551 $limit = ''; 552 553 //# run this in batches. With many bounces this query runs OOM 554 $bounceCount = Sql_Fetch_Row_Query(sprintf('select count(*) from %s', $GLOBALS['tables']['user_message_bounce'])); 555 $total = $bounceCount[0]; 556 $counter = 0; 557 $batchSize = 500; //# @TODO make a config, to allow tweaking on bigger systems 558 while ($counter < $total) { 559 $limit = ' limit '.$counter.', '.$batchSize; 560 $counter += $batchSize; 561 cl_progress(s('processed %d out of %d bounces for advanced bounce rules', $counter, $total)); 562 563 $req = Sql_Query(sprintf('select * from %s as bounce, %s as umb where bounce.id = umb.bounce %s', 564 $GLOBALS['tables']['bounce'], $GLOBALS['tables']['user_message_bounce'], $limit)); 565 while ($row = Sql_Fetch_Array($req)) { 566 $alive = checkLock($process_id); 567 if ($alive) { 568 keepLock($process_id); 569 } else { 570 bounceProcessError($GLOBALS['I18N']->get('Process Killed by other process')); 571 } 572 // cl_output(memory_get_usage()); 573 574 // outputProcessBounce('User '.$row['user']); 575 $rule = matchBounceRules($row['header']."\n\n".$row['data'], $bouncerules); 576 // outputProcessBounce('Action '.$rule['action']); 577 // outputProcessBounce('Rule'.$rule['id']); 578 $userdata = array(); 579 if ($rule && is_array($rule)) { 580 if ($row['user']) { 581 $userdata = Sql_Fetch_Array_Query("select * from {$tables['user']} where id = ".$row['user']); 582 } 583 $report_linkroot = $GLOBALS['admin_scheme'].'://'.$GLOBALS['website'].$GLOBALS['adminpages']; 584 585 Sql_Query(sprintf('update %s set count = count + 1 where id = %d', 586 $GLOBALS['tables']['bounceregex'], $rule['id'])); 587 Sql_Query(sprintf('insert ignore into %s (regex,bounce) values(%d,%d)', 588 $GLOBALS['tables']['bounceregex_bounce'], $rule['id'], $row['bounce'])); 589 590 //17860 - check the current status to avoid doing it over and over 591 $currentStatus = Sql_Fetch_Assoc_Query(sprintf('select confirmed,blacklisted from %s where id = %d', $GLOBALS['tables']['user'],$row['user'])); 592 $confirmed = !empty($currentStatus['confirmed']); 593 $blacklisted = !empty($currentStatus['blacklisted']); 594 595 switch ($rule['action']) { 596 case 'deleteuser': 597 logEvent('User '.$userdata['email'].' deleted by bounce rule '.PageLink2('bouncerule&id='.$rule['id'], 598 $rule['id'])); 599 $advanced_report .= 'User '.$userdata['email'].' deleted by bounce rule '.$rule['id'].PHP_EOL; 600 $advanced_report .= 'User: '.$report_linkroot.'/?page=user&id='.$userdata['id'].PHP_EOL; 601 $advanced_report .= 'Rule: '.$report_linkroot.'/?page=bouncerule&id='.$rule['id'].PHP_EOL; 602 deleteUser($row['user']); 603 break; 604 case 'unconfirmuser': 605 if ($confirmed) { 606 logEvent('User ' . $userdata['email'] . ' unconfirmed by bounce rule ' . PageLink2('bouncerule&id=' . $rule['id'], 607 $rule['id'])); 608 Sql_Query(sprintf('update %s set confirmed = 0 where id = %d', $GLOBALS['tables']['user'], 609 $row['user'])); 610 $advanced_report .= 'User ' . $userdata['email'] . ' made unconfirmed by bounce rule ' . $rule['id'] . PHP_EOL; 611 $advanced_report .= 'User: ' . $report_linkroot . '/?page=user&id=' . $userdata['id'] . PHP_EOL; 612 $advanced_report .= 'Rule: ' . $report_linkroot . '/?page=bouncerule&id=' . $rule['id'] . PHP_EOL; 613 addUserHistory($userdata['email'], s('Auto Unconfirmed'), 614 s('Subscriber auto unconfirmed for') . ' ' . s('bounce rule') . ' ' . $rule['id']); 615 addSubscriberStatistics('auto unsubscribe', 1); 616 } 617 break; 618 case 'deleteuserandbounce': 619 logEvent('User '.$userdata['email'].' deleted by bounce rule '.PageLink2('bouncerule&id='.$rule['id'], 620 $rule['id'])); 621 $advanced_report .= 'User '.$userdata['email'].' deleted by bounce rule '.$rule['id'].PHP_EOL; 622 $advanced_report .= 'User: '.$report_linkroot.'/?page=user&id='.$userdata['id'].PHP_EOL; 623 $advanced_report .= 'Rule: '.$report_linkroot.'/?page=bouncerule&id='.$rule['id'].PHP_EOL; 624 deleteUser($row['user']); 625 deleteBounce($row['bounce']); 626 break; 627 case 'unconfirmuseranddeletebounce': 628 if ($confirmed) { 629 logEvent('User ' . $userdata['email'] . ' unconfirmed by bounce rule ' . PageLink2('bouncerule&id=' . $rule['id'], 630 $rule['id'])); 631 Sql_Query(sprintf('update %s set confirmed = 0 where id = %d', $GLOBALS['tables']['user'], 632 $row['user'])); 633 $advanced_report .= 'User ' . $userdata['email'] . ' made unconfirmed by bounce rule ' . $rule['id'] . PHP_EOL; 634 $advanced_report .= 'User: ' . $report_linkroot . '/?page=user&id=' . $userdata['id'] . PHP_EOL; 635 $advanced_report .= 'Rule: ' . $report_linkroot . '/?page=bouncerule&id=' . $rule['id'] . PHP_EOL; 636 addUserHistory($userdata['email'], s('Auto unconfirmed'), 637 s('Subscriber auto unconfirmed for') . ' ' . $GLOBALS['I18N']->get('bounce rule') . ' ' . $rule['id']); 638 addSubscriberStatistics('auto unsubscribe', 1); 639 } 640 deleteBounce($row['bounce']); 641 break; 642 case 'blacklistuser': 643 if (!$blacklisted) { 644 logEvent('User ' . $userdata['email'] . ' blacklisted by bounce rule ' . PageLink2('bouncerule&id=' . $rule['id'], 645 $rule['id'])); 646 addUserToBlacklist($userdata['email'], 647 s('Subscriber auto blacklisted by bounce rule', $rule['id'])); 648 $advanced_report .= 'User ' . $userdata['email'] . ' blacklisted by bounce rule ' . $rule['id'] . PHP_EOL; 649 $advanced_report .= 'User: ' . $report_linkroot . '/?page=user&id=' . $userdata['id'] . PHP_EOL; 650 $advanced_report .= 'Rule: ' . $report_linkroot . '/?page=bouncerule&id=' . $rule['id'] . PHP_EOL; 651 addUserHistory($userdata['email'], $GLOBALS['I18N']->get('Auto Unsubscribed'), 652 $GLOBALS['I18N']->get('User auto unsubscribed for') . ' ' . $GLOBALS['I18N']->get('bounce rule') . ' ' . $rule['id']); 653 addSubscriberStatistics('auto blacklist', 1); 654 } 655 break; 656 case 'blacklistuseranddeletebounce': 657 if (!$blacklisted) { 658 logEvent('User ' . $userdata['email'] . ' blacklisted by bounce rule ' . PageLink2('bouncerule&id=' . $rule['id'], 659 $rule['id'])); 660 addUserToBlacklist($userdata['email'], 661 s('Subscriber auto blacklisted by bounce rule %d', $rule['id'])); 662 $advanced_report .= 'User ' . $userdata['email'] . ' blacklisted by bounce rule ' . $rule['id'] . PHP_EOL; 663 $advanced_report .= 'User: ' . $report_linkroot . '/?page=user&id=' . $userdata['id'] . PHP_EOL; 664 $advanced_report .= 'Rule: ' . $report_linkroot . '/?page=bouncerule&id=' . $rule['id'] . PHP_EOL; 665 addUserHistory($userdata['email'], $GLOBALS['I18N']->get('Auto Unsubscribed'), 666 $GLOBALS['I18N']->get('User auto unsubscribed for') . ' ' . $GLOBALS['I18N']->get('bounce rule') . ' ' . $rule['id']); 667 addSubscriberStatistics('auto blacklist', 1); 668 } 669 deleteBounce($row['bounce']); 670 break; 671 case 'blacklistemail': 672 logEvent('email '.$userdata['email'].' blacklisted by bounce rule '.PageLink2('bouncerule&id='.$rule['id'], 673 $rule['id'])); 674 addEmailToBlackList($userdata['email'], 675 s('Email address auto blacklisted by bounce rule %d', $rule['id'])); 676 $advanced_report .= 'email '.$userdata['email'].' blacklisted by bounce rule '.$rule['id'].PHP_EOL; 677 $advanced_report .= 'User: '.$report_linkroot.'/?page=user&id='.$userdata['id'].PHP_EOL; 678 $advanced_report .= 'Rule: '.$report_linkroot.'/?page=bouncerule&id='.$rule['id'].PHP_EOL; 679 addUserHistory($userdata['email'], $GLOBALS['I18N']->get('Auto Unsubscribed'), 680 $GLOBALS['I18N']->get('email auto unsubscribed for').' '.$GLOBALS['I18N']->get('bounce rule').' '.$rule['id']); 681 addSubscriberStatistics('auto blacklist', 1); 682 break; 683 case 'blacklistemailanddeletebounce': 684 logEvent('email '.$userdata['email'].' blacklisted by bounce rule '.PageLink2('bouncerule&id='.$rule['id'], 685 $rule['id'])); 686 addEmailToBlackList($userdata['email'], 687 s('Email address auto blacklisted by bounce rule %d', $rule['id'])); 688 $advanced_report .= 'email '.$userdata['email'].' blacklisted by bounce rule '.$rule['id'].PHP_EOL; 689 $advanced_report .= 'User: '.$report_linkroot.'/?page=user&id='.$userdata['id'].PHP_EOL; 690 $advanced_report .= 'Rule: '.$report_linkroot.'/?page=bouncerule&id='.$rule['id'].PHP_EOL; 691 addUserHistory($userdata['email'], $GLOBALS['I18N']->get('Auto Unsubscribed'), 692 $GLOBALS['I18N']->get('User auto unsubscribed for').' '.$GLOBALS['I18N']->get('bounce rule').' '.$rule['id']); 693 addSubscriberStatistics('auto blacklist', 1); 694 deleteBounce($row['bounce']); 695 break; 696 case 'deletebounce': 697 deleteBounce($row['bounce']); 698 if (REPORT_DELETED_BOUNCES == 1) { 699 $advanced_report .= 'Deleted bounce ' . $userdata['email'] . ' --> Bounce deleted by bounce rule ' . $rule['id'] . PHP_EOL; 700 } 701 break; 702 } 703 704 ++$matched; 705 } else { 706 ++$notmatched; 707 } 708 } 709 } 710 outputProcessBounce($matched.' '.$GLOBALS['I18N']->get('bounces processed by advanced processing')); 711 outputProcessBounce($notmatched.' '.$GLOBALS['I18N']->get('bounces were not matched by advanced processing rules')); 712} 713 714// have a look who should be flagged as unconfirmed 715outputProcessBounce($GLOBALS['I18N']->get('Identifying consecutive bounces')); 716 717// we only need users who are confirmed at the moment 718$userid_req = Sql_query(sprintf('select distinct umb.user from %s umb, %s u 719 where u.id = umb.user and u.confirmed and !u.blacklisted', 720 $tables['user_message_bounce'], 721 $tables['user'] 722)); 723$total = Sql_Affected_Rows(); 724if (!$total) { 725 outputProcessBounce($GLOBALS['I18N']->get('Nothing to do')); 726} 727 728$usercnt = 0; 729$unsubscribed_users = ''; 730while ($user = Sql_Fetch_Row($userid_req)) { 731 keepLock($process_id); 732 set_time_limit(600); 733 //$msg_req = Sql_Query(sprintf('select * from 734 //%s um left join %s umb on (um.messageid = umb.message and userid = user) 735 //where userid = %d and um.status = "sent" 736 //order by entered desc', 737 //$tables["usermessage"],$tables["user_message_bounce"], 738 //$user[0])); 739 740 //# 17361 - update of the above query, to include the bounce table and to exclude duplicate bounces 741 $msg_req = Sql_Query(sprintf('select umb.*,um.*,b.status,b.comment from %s um left join %s umb on (um.messageid = umb.message and userid = user) 742 left join %s b on umb.bounce = b.id 743 where userid = %d and um.status = "sent" 744 order by entered desc', 745 $tables['usermessage'], $tables['user_message_bounce'], $tables['bounce'], 746 $user[0])); 747 748 /* $cnt = 0; 749 $alive = 1;$removed = 0; 750 while ($alive && !$removed && $bounce = Sql_Fetch_Array($msg_req)) { 751 $alive = checkLock($process_id); 752 if ($alive) 753 keepLock($process_id); 754 else 755 bounceProcessError($GLOBALS['I18N']->get("Process Killed by other process")); 756 if (sprintf('%d',$bounce["bounce"]) == $bounce["bounce"]) { 757 $cnt++; 758 if ($cnt >= $bounce_unsubscribe_threshold) { 759 $removed = 1; 760 outputProcessBounce(sprintf('unsubscribing %d -> %d bounces',$user[0],$cnt)); 761 $userurl = PageLink2("user&id=$user[0]",$user[0]); 762 logEvent($GLOBALS['I18N']->get("User")." $userurl ".$GLOBALS['I18N']->get("has consecutive bounces")." ($cnt) ".$GLOBALS['I18N']->get("over threshold, user marked unconfirmed")); 763 $emailreq = Sql_Fetch_Row_Query("select email from {$tables["user"]} where id = $user[0]"); 764 addUserHistory($emailreq[0],$GLOBALS['I18N']->get("Auto Unsubscribed"),$GLOBALS['I18N']->get("User auto unsubscribed for")." $cnt ".$GLOBALS['I18N']->get("consecutive bounces")); 765 Sql_Query(sprintf('update %s set confirmed = 0 where id = %d',$tables["user"],$user[0])); 766 addSubscriberStatistics('auto unsubscribe',1); 767 $email_req = Sql_Fetch_Row_Query(sprintf('select email from %s where id = %d',$tables["user"],$user[0])); 768 $unsubscribed_users .= $email_req[0] . " [$user[0]] ($cnt)\n"; 769 } 770 } elseif ($bounce["bounce"] == "") { 771 $cnt = 0; 772 } 773 }*/ 774 //$alive = 1;$removed = 0; DT 051105 775 $cnt = 0; 776 $alive = 1; 777 $removed = $msgokay = $unconfirmed = $unsubscribed = 0; 778 //while ($alive && !$removed && $bounce = Sql_Fetch_Array($msg_req)) { DT 051105 779 while ($alive && !$removed && !$msgokay && $bounce = Sql_Fetch_Array($msg_req)) { 780 $alive = checkLock($process_id); 781 if ($alive) { 782 keepLock($process_id); 783 } else { 784 bounceProcessError('Process Killed by other process'); 785 } 786 787 if (stripos($bounce['status'], 'duplicate') === false && stripos($bounce['comment'], 'duplicate') === false) { 788 if (sprintf('%d', $bounce['bounce']) == $bounce['bounce']) { 789 ++$cnt; 790 if ($cnt >= $bounce_unsubscribe_threshold) { 791 if (!$unsubscribed) { 792 outputProcessBounce(sprintf('unsubscribing %d -> %d bounces', $user[0], $cnt)); 793 $userurl = PageLink2("user&id=$user[0]", $user[0]); 794 logEvent(s('User (url:%s) has consecutive bounces (%d) over threshold (%d), user marked unconfirmed', 795 $userurl, $cnt, $bounce_unsubscribe_threshold)); 796 $emailreq = Sql_Fetch_Row_Query("select email from {$tables['user']} where id = $user[0]"); 797 addUserHistory($emailreq[0], s('Auto Unconfirmed'), 798 s('Subscriber auto unconfirmed for %d consecutive bounces', $cnt)); 799 Sql_Query(sprintf('update %s set confirmed = 0 where id = %d', $tables['user'], $user[0])); 800 $email_req = Sql_Fetch_Row_Query(sprintf('select email from %s where id = %d', $tables['user'], 801 $user[0])); 802 $unsubscribed_users .= $email_req[0]."\t\t($cnt)\t\t".$GLOBALS['scheme'].'://'.getConfig('website').$GLOBALS['adminpages'].'/?page=user&id='.$user[0].PHP_EOL; 803 $unsubscribed = 1; 804 } 805 if (BLACKLIST_EMAIL_ON_BOUNCE && $cnt >= BLACKLIST_EMAIL_ON_BOUNCE) { 806 $removed = 1; 807 //0012262: blacklist email when email bounces 808 cl_output(s('%d consecutive bounces, threshold reached, blacklisting subscriber', $cnt)); 809 addEmailToBlackList($emailreq[0], s('%d consecutive bounces, threshold reached', $cnt)); 810 } 811 } 812 } elseif ($bounce['bounce'] == '') { 813 //$cnt = 0; DT 051105 814 $cnt = 0; 815 $msgokay = 1; //DT 051105 - escaping loop if message received okay 816 } 817 } 818 } 819 if ($usercnt % 5 == 0) { 820 // outputProcessBounce($GLOBALS['I18N']->get("Identifying consecutive bounces")); 821 cl_progress(s('processed %d out of %d subscribers', $usercnt, $total), 1); 822 } 823 ++$usercnt; 824 flush(); 825} 826 827//outputProcessBounce($GLOBALS['I18N']->get("Identifying consecutive bounces")); 828outputProcessBounce(PHP_EOL.s('total of %d subscribers processed', $total).' '); 829 830$report = ''; 831 832if ($advanced_report) { 833 $report .= $GLOBALS['I18N']->get('Report of advanced bounce processing:')."\n$advanced_report\n"; 834} 835if ($unsubscribed_users) { 836 $report .= PHP_EOL.$GLOBALS['I18N']->get('Below are users who have been marked unconfirmed. The number in () is the number of consecutive bounces.').PHP_EOL; 837 $report .= "\n$unsubscribed_users"; 838} 839if ($report) { 840 $report = $GLOBALS['I18N']->get('Report:')."\n$download_report\n".$report; 841} 842// shutdown will take care of reporting 843//finish("info",$report); 844 845// IMAP errors following when Notices are on are a PHP bug 846# http://bugs.php.net/bug.php?id=7207 847