/** * @throws \Exception */ public function startProcessing() { $this->preflightCheck(); # lets not do this unless we do some locking first register_shutdown_function(array(&$this, 'processBouncesShutdown')); ignore_user_abort(1); if (Config::get('commandline', false) !== false && Config::get('commandline_force', false) !== false) { # force set, so kill other processes phpList::log()->info('Force set, killing other send processes', ['page' => 'procesbounces']); $this->process_id = Process::getPageLock('BounceProcessor', true); } else { $this->process_id = Process::getPageLock('BounceProcessor'); } if (empty($this->process_id)) { return; } switch (Config::BOUNCE_PROTOCOL) { case 'pop': $this->processPop(Config::BOUNCE_MAILBOX_HOST, Config::BOUNCE_MAILBOX_USER, Config::BOUNCE_MAILBOX_PASSWORD); break; case 'mbox': $this->processMbox(Config::BOUNCE_MAILBOX); break; default: phpList::log()->critical(s('bounce_protocol not supported'), ['page' => 'processbounces']); return; } # now we have filled database with all available bounces ## reprocess the unidentified ones, as the bounce detection has improved, so it might catch more phpList::log()->info('reprocessing', ['page' => 'procesbounces']); $reparsed = $count = 0; $reidentified = 0; $result = phpList::DB()->query(sprintf('SELECT * FROM %s WHERE status = "unidentified bounce"', Config::getTableName('bounce'))); $total = $result->rowCount(); phpList::log()->info(s('%d bounces to reprocess', $total), ['page' => 'procesbounces']); while ($bounce = $result->fetch(\PDO::FETCH_ASSOC)) { $count++; if ($count % 25 == 0) { Output::cl_progress(s('%d out of %d processed', $count, $total)); } $bounceBody = $this->decodeBody($bounce->header, $bounce->data); $subscriber = $this->findSubscriber($bounceBody); $campaign_id = $this->findCampaignId($bounceBody); if ($subscriber !== false || !empty($campaign_id)) { $reparsed++; if ($this->processBounceData($bounce['id'], $campaign_id, $subscriber->id)) { $reidentified++; } } } phpList::log()->info(s('%d out of %d processed', $count, $total), ['page' => 'procesbounces']); if (Config::VERBOSE) { $this->output(s('%d bounces were re-processed and %d bounces were re-identified', $reparsed, $reidentified)); } $advanced_report = ''; if (Config::USE_ADVANCED_BOUNCEHANDLING) { $this->output(s('Processing bounces based on active bounce rules')); $bouncerules = BounceRule::getAllBounceRules(); $matched = 0; $notmatched = 0; //$limit = ' limit 10'; //$limit = ' limit 10000'; $limit = ''; # @@ not sure whether this one would make sense # $result = Sql_Query(sprintf('select * from %s as bounce, %s as umb,%s as user where bounce.id = umb.bounce # and user.id = umb.user and !user.confirmed and !user.blacklisted %s', # $GLOBALS['tables']['bounce'],$GLOBALS['tables']['user_message_bounce'],$GLOBALS['tables']['user'],$limit)); $result = phpList::DB()->query(sprintf('SELECT * FROM %s AS bounce, %s AS umb WHERE bounce.id = umb.bounce %s', Config::getTableName('bounce'), Config::getTableName('user_message_bounce'), $limit)); while ($row = $result->fetch(\PDO::FETCH_ASSOC)) { $alive = Process::checkLock($this->process_id); if ($alive) { Process::keepLock($this->process_id); } else { $this->bounceProcessError(s('Process Killed by other process')); } # output('Subscriber '.$row['user']); $rule = BounceRule::matchedBounceRules($row['data'], $bouncerules); # output('Action '.$rule['action']); # output('Rule'.$rule['id']); if ($rule && is_array($rule)) { if ($row['user']) { $subscriber = Subscriber::getSubscriber($row['user']); } $report_linkroot = Config::get('admin_scheme') . '://' . Config::get('website') . Config::get('adminpages'); phpList::DB()->query(sprintf('UPDATE %s SET count = count + 1 WHERE id = %d', Config::getTableName('bounceregex'), $rule['id'])); phpList::DB()->query(sprintf('INSERT IGNORE INTO %s (regex,bounce) VALUES(%d,%d)', Config::getTableName('bounceregex_bounce'), $rule['id'], $row['bounce'])); switch ($rule['action']) { case 'deletesubscriber': phpList::log()->notice('Subscriber ' . $subscriber->getEmailAddress() . ' deleted by bounce rule ' . PageLink2('bouncerule&id=' . $rule['id'], $rule['id'])); $advanced_report .= 'Subscriber ' . $subscriber->getEmailAddress() . ' deleted by bounce rule ' . $rule['id'] . "\n"; $advanced_report .= 'Subscriber: ' . $report_linkroot . '/?page=subscriber&id=' . $subscriber->id . "\n"; $advanced_report .= 'Rule: ' . $report_linkroot . '/?page=bouncerule&id=' . $rule['id'] . "\n"; $subscriber->delete(); break; case 'unconfirmsubscriber': phpList::log()->notice('Subscriber ' . $subscriber->getEmailAddress() . ' unconfirmed by bounce rule ' . PageLink2('bouncerule&id=' . $rule['id'], $rule['id'])); $subscriber->confirmed = 0; $subscriber->update(); $advanced_report .= 'Subscriber ' . $subscriber->getEmailAddress() . ' made unconfirmed by bounce rule ' . $rule['id'] . "\n"; $advanced_report .= 'Subscriber: ' . $report_linkroot . '/?page=subscriber&id=' . $subscriber->id . "\n"; $advanced_report .= 'Rule: ' . $report_linkroot . '/?page=bouncerule&id=' . $rule['id'] . "\n"; $subscriber->addHistory(s('Auto Unconfirmed'), s('Subscriber auto unconfirmed for') . " " . s('bounce rule') . ' ' . $rule['id'], $subscriber->id); Util::addSubscriberStatistics('auto unsubscribe', 1); break; case 'deletesubscriberandbounce': phpList::log()->notice('Subscriber ' . $row['user'] . ' deleted by bounce rule ' . PageLink2('bouncerule&id=' . $rule['id'], $rule['id'])); $advanced_report .= 'Subscriber ' . $subscriber->getEmailAddress() . ' deleted by bounce rule ' . $rule['id'] . "\n"; $advanced_report .= 'Subscriber: ' . $report_linkroot . '/?page=subscriber&id=' . $subscriber->id . "\n"; $advanced_report .= 'Rule: ' . $report_linkroot . '/?page=bouncerule&id=' . $rule['id'] . "\n"; $subscriber->delete(); Bounce::deleteBounce($row['bounce']); break; case 'unconfirmsubscriberanddeletebounce': phpList::log()->notice('Subscriber ' . $subscriber->getEmailAddress() . ' unconfirmed by bounce rule ' . PageLink2('bouncerule&id=' . $rule['id'], $rule['id'])); $subscriber->confirmed = 0; $subscriber->update(); $advanced_report .= 'Subscriber ' . $subscriber->getEmailAddress() . ' made unconfirmed by bounce rule ' . $rule['id'] . "\n"; $advanced_report .= 'Subscriber: ' . $report_linkroot . '/?page=subscriber&id=' . $subscriber->id . "\n"; $advanced_report .= 'Rule: ' . $report_linkroot . '/?page=bouncerule&id=' . $rule['id'] . "\n"; $subscriber->addHistory(s('Auto unconfirmed'), s('Subscriber auto unconfirmed for') . " " . s("bounce rule") . ' ' . $rule['id'], $subscriber->id); Util::addSubscriberStatistics('auto unsubscribe', 1); Bounce::deleteBounce($row['bounce']); break; case 'blacklistsubscriber': phpList::log()->notice('Subscriber ' . $subscriber->getEmailAddress() . ' blacklisted by bounce rule ' . PageLink2('bouncerule&id=' . $rule['id'], $rule['id'])); $subscriber->blacklistSubscriber($subscriber->getEmailAddress(), s("Auto Blacklisted"), s("Subscriber auto blacklisted for") . " " . s("bounce rule") . ' ' . $rule['id']); $advanced_report .= 'Subscriber ' . $subscriber->getEmailAddress() . ' blacklisted by bounce rule ' . $rule['id'] . "\n"; $advanced_report .= 'Subscriber: ' . $report_linkroot . '/?page=subscriber&id=' . $subscriber->id . "\n"; $advanced_report .= 'Rule: ' . $report_linkroot . '/?page=bouncerule&id=' . $rule['id'] . "\n"; $subscriber->addHistory(s("Auto Unsubscribed"), s("Subscriber auto unsubscribed for") . " " . s("bounce rule") . ' ' . $rule['id'], $subscriber->id); Util::addSubscriberStatistics('auto blacklist', 1); break; case 'blacklistsubscriberanddeletebounce': phpList::log()->notice('Subscriber ' . $subscriber->getEmailAddress() . ' blacklisted by bounce rule ' . PageLink2('bouncerule&id=' . $rule['id'], $rule['id'])); $subscriber->blacklistSubscriber($subscriber->getEmailAddress(), s("Auto Blacklisted"), s("Subscriber auto blacklisted for") . " " . s("bounce rule") . ' ' . $rule['id']); $advanced_report .= 'Subscriber ' . $subscriber->getEmailAddress() . ' blacklisted by bounce rule ' . $rule['id'] . "\n"; $advanced_report .= 'Subscriber: ' . $report_linkroot . '/?page=subscriber&id=' . $subscriber->id . "\n"; $advanced_report .= 'Rule: ' . $report_linkroot . '/?page=bouncerule&id=' . $rule['id'] . "\n"; $subscriber->addHistory(s("Auto Unsubscribed"), s("Subscriber auto unsubscribed for") . " " . s("bounce rule") . ' ' . $rule['id'], $subscriber->id); Util::addSubscriberStatistics('auto blacklist', 1); Bounce::deleteBounce($row['bounce']); break; case 'deletebounce': Bounce::deleteBounce($row['bounce']); break; } $matched++; } else { $notmatched++; } } $this->output($matched . ' ' . s('bounces processed by advanced processing')); $this->output($notmatched . ' ' . s('bounces were not matched by advanced processing rules')); } # have a look who should be flagged as unconfirmed $this->output(s("Identifying consecutive bounces")); # we only need subscriber who are confirmed at the moment $subscriberid_result = phpList::DB()->query(sprintf('SELECT DISTINCT umb.user FROM %s AS umb, %s AS u WHERE u.id = umb.user AND u.confirmed', Config::getTableName('user_message_bounce'), Config::getTableName('user', true))); if ($subscriberid_result->rowCount() <= 0) { $this->output(s("Nothing to do")); } $subscribercnt = 0; $unsubscribed_subscribers = ""; while ($subscriber = $subscriberid_result->fetch(\PDO::FETCH_ASSOC)) { Process::keepLock($this->process_id); set_time_limit(600); $msg_req = phpList::DB()->query(sprintf('SELECT * FROM %s AS um LEFT JOIN %s AS umb ON (um.messageid = umb.message AND userid = user) WHERE userid = %d AND um.status = "sent" ORDER BY entered DESC', Config::getTableName('usermessage'), Config::getTableName('user_message_bounce'), $subscriber[0])); /* $cnt = 0; $alive = 1;$removed = 0; while ($alive && !$removed && $bounce = Sql_Fetch_Array($msg_req)) { $alive = checkLock($process_id); if ($alive) keepLock($process_id); else ProcessError(s("Process Killed by other process")); if (sprintf('%d',$bounce['bounce']) == $bounce['bounce']) { $cnt++; if ($cnt >= $bounce_unsubscribe_threshold) { $removed = 1; output(sprintf('unsubscribing %d -> %d bounces',$subscriber[0],$cnt)); $subscriberurl = PageLink2("user&id=$subscriber[0]",$subscriber[0]); logEvent(s("Subscriber")." $subscriberurl ".s("has consecutive bounces")." ($cnt) ".s("over threshold, user marked unconfirmed")); $emailreq = Sql_Fetch_Row_Query("select email from {$tables['user']} where id = $subscriber[0]"); addSubscriberHistory($emailreq[0],s("Auto Unsubscribed"),s("Subscriber auto unsubscribed for")." $cnt ".s("consecutive bounces")); Sql_Query(sprintf('update %s set confirmed = 0 where id = %d',$tables['user'],$subscriber[0])); addSubscriberStatistics('auto unsubscribe',1); $email_req = Sql_Fetch_Row_Query(sprintf('select email from %s where id = %d',$tables['user'],$subscriber[0])); $unsubscribed_users .= $email_req[0] . " [$subscriber[0]] ($cnt)\n"; } } elseif ($bounce['bounce'] == "") { $cnt = 0; } }*/ #$alive = 1;$removed = 0; DT 051105 $cnt = 0; $alive = 1; $removed = $msgokay = $unconfirmed = $unsubscribed = 0; #while ($alive && !$removed && $bounce = Sql_Fetch_Array($msg_req)) { DT 051105 while ($alive && !$removed && !$msgokay && ($bounce = $msg_req->fetch(\PDO::FETCH_ASSOC))) { $alive = Process::checkLock($this->process_id); if ($alive) { Process::keepLock($this->process_id); } else { $this->bounceProcessError('Process Killed by other process'); } if (sprintf('%d', $bounce['bounce']) == $bounce['bounce']) { $cnt++; if ($cnt >= Config::BOUNCE_UNSUBSCRIBE_THRESHOLD) { if (!$unsubscribed) { $this->output(sprintf('unsubscribing %d -> %d bounces', $subscriber[0], $cnt)); $subscriberurl = PageLink2("user&id={$subscriber['0']}", $subscriber[0]); phpList::log()->notice(s('Subscriber (url:%s) has consecutive bounces (%d) over threshold (%d), user marked unconfirmed', $subscriberurl, $cnt, Config::BOUNCE_UNSUBSCRIBE_THRESHOLD)); Subscriber::addHistory(s('Auto Unconfirmed'), s('Subscriber auto unconfirmed for %d consecutive bounces', $cnt), $subscriber[0]); phpList::DB()->query(sprintf('UPDATE %s SET confirmed = 0 WHERE id = %d', Config::getTableName('user', true), $subscriber[0])); $email_req = phpList::DB()->query(sprintf('SELECT email FROM %s WHERE id = %d', Config::getTableName('user', true), $subscriber[0])); $unsubscribed_subscribers .= $email_req[0] . "\t\t({$cnt})\t\t" . Config::get('scheme') . '://' . Config::get('website') . Config::get('adminpages') . '/?page=user&id=' . $subscriber[0] . "\n"; $unsubscribed = 1; } if (Config::get('BLACKLIST_EMAIL_ON_BOUNCE') && $cnt >= Config::get('BLACKLIST_EMAIL_ON_BOUNCE')) { $removed = 1; #0012262: blacklist email when email bounces phpList::log()->info(s('%d consecutive bounces, threshold reached, blacklisting subscriber', $cnt), ['page' => 'procesbounces']); Subscriber::blacklistSubscriber($subscriber[0], s('%d consecutive bounces, threshold reached', $cnt)); } } } elseif ($bounce['bounce'] == '') { #$cnt = 0; DT 051105 $cnt = 0; $msgokay = 1; #DT 051105 - escaping loop if message received okay } } if ($subscribercnt % 5 == 0) { # output(s("Identifying consecutive bounces")); phpList::log()->info(s('processed %d out of %d subscribers', $subscribercnt, $total), ['page' => 'procesbounces']); } $subscribercnt++; } #output(s("Identifying consecutive bounces")); $this->output("\n" . s('total of %d subscribers processed', $total) . ' '); /* $report = ''; if (phpList::log()->getReport()) { $report .= s('Report:')."\n" . phpList::log()->getReport() . "\n"; } if ($advanced_report) { $report .= s('Report of advanced bounce processing:')."\n$advanced_report\n"; } if ($unsubscribed_subscribers) { $report .= "\n".s('Below are subscribers who have been marked unconfirmed. The in () is the number of consecutive bounces.')."\n"; $report .= "\n$unsubscribed_subscribers"; } else { # don't send a report email, if only some bounces were downloaded, but no subscribers unsubscribed. $report = ''; } # shutdown will take care of reporting #finish("info",$report); */ # IMAP errors following when Notices are on are a PHP bug # http://bugs.php.net/bug.php?id=7207 }