/** * @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 }
/** * Process campaign queue * @param bool $force set true if this one has to cancel running send processes * @param bool $reload * @param int $cmd_max * @return bool */ public function startProcessing($force = false, $reload = false, $cmd_max = 0) { //initialize the process queue timer Timer::start('process_queue'); $commandline = Config::get('commandline', false); if ($commandline && $force) { # force set, so kill other processes phpList::log()->info('Force set, killing other send processes', ['page' => 'processqueue']); } $this->send_process_id = Process::getPageLock('processqueue', $force); if (empty($this->send_process_id)) { phpList::log()->debug(s('Unable get lock for processing'), ['page' => 'porcessqueue']); $this->status = s('Error processing'); return false; } #Output::cl_output('page locked on '.$this->send_process_id); $this->reload = $reload; //TODO: enable plugins /*foreach ($GLOBALS['plugins'] as $pluginname => $plugin) { $plugin->processQueueStart(); }*/ ## let's make sure all subscribers have a uniqid ## only when on CL if ($commandline) { $num = Subscriber::checkUniqueIds(); if ($num) { phpList::log()->info('Given a Unique ID to ' . $num . ' subscribers, this might have taken a while', ['page' => 'processqueue']); } } $this->num_per_batch = 0; $this->batch_period = 0; $somesubscribers = 0; $restrictions = $this->checkRestrictions($cmd_max); flush(); # report keeps track of what is going on $this->nothingtodo = false; register_shutdown_function(array(&$this, 'shutdown')); # we do not want to timeout or abort ignore_user_abort(1); set_time_limit(600); flush(); if (!$this->reload) { ## only show on first load phpList::log()->debug(s('Started'), 0, ['page' => 'porcessqueue']); if (Config::get('SYSTEM_TIMEZONE') != '') { phpList::log()->debug(s('Time now ') . date('Y-m-d H:i'), ['page' => 'porcessqueue']); } } #output('Will process for a maximum of '.$restrictions['max_process_queue_time'].' seconds '); if (!$this->reload) { ## only show on first load if (!empty($restrictions['rules'])) { phpList::log()->debug($restrictions['rules'], ['page' => 'porcessqueue']); } if ($restrictions['locked']) { $this->queueProcessError(s('Processing has been suspended by your ISP, please try again later'), 1); } } if ($this->num_per_batch > 0) { if ($this->original_num_per_batch != $this->num_per_batch) { if (empty($reload)) { phpList::log()->debug(s('Sending in batches of %d campaigns', $this->original_num_per_batch), 0, ['page' => 'porcessqueue']); } $diff = $this->original_num_per_batch - $this->num_per_batch; if ($diff < 0) { $diff = 0; } phpList::log()->info(s('This batch will be %d emails, because in the last %d seconds %d emails were sent', $this->num_per_batch, $this->batch_period, $diff), ['page' => 'processqueue']); } else { phpList::log()->debug(s('Sending in batches of %d emails', $this->num_per_batch), ['page' => 'porcessqueue']); } } elseif ($this->num_per_batch < 0) { phpList::log()->info(s('In the last %d seconds more emails were sent (%d) than is currently allowed per batch (%d)', $this->batch_period, $restrictions['recently_sent'], $this->original_num_per_batch), ['page' => 'processqueue']); $this->processed = 0; $this->script_stage = 5; Config::setRunningConfig('wait', $this->batch_period); return false; } ${$this}->counters['batch_total'] = $this->num_per_batch; if (0 && $reload) { //TODO: change change this to use running config instead of $_GET $lastsent = !empty($_GET['lastsent']) ? sprintf('%d', $_GET['lastsent']) : 0; $lastskipped = !empty($_GET['lastskipped']) ? sprintf('%d', $_GET['lastskipped']) : 0; phpList::log()->debug(s('Sent in last run') . ": {$lastsent}", ['page' => 'porcessqueue']); phpList::log()->debug(s('Skipped in last run') . ": {$lastskipped}", ['page' => 'porcessqueue']); } $this->script_stage = 1; # we are active $this->notsent = $this->sent = $this->invalid = $this->unconfirmed = $this->cannotsend = 0; ## check for campaigns that need requeuing Campaign::checkCampaignsToRequeue(); if (Config::VERBOSE) { phpList::log()->debug(phpList::DB()->getLastQuery(), ['page' => 'porcessqueue']); } $campaigns = Campaign::getCampaignsToQueue(); $num_campaigns = count($campaigns); if ($num_campaigns) { if (!$this->reload) { phpList::log()->debug(s('Processing has started,') . ' ' . $num_campaigns . ' ' . s('campaign(s) to process.'), ['page' => 'porcessqueue']); } Cache::clearPageCache(); if (!$commandline && !$this->reload) { if (!$this->safemode) { phpList::log()->info(s('Please leave this window open. You have batch processing enabled, so it will reload several times to send the campaigns. Reports will be sent by email to') . ' ' . Config::get('report_address')); } else { phpList::log()->info(s('Your webserver is running in safe_mode. Please keep this window open. It may reload several times to make sure all campaigns are sent.') . ' ' . s('Reports will be sent by email to') . ' ' . Config::get('report_address')); } } } $this->script_stage = 2; # we know the campaigns to process #include_once "footer.inc"; if (!isset($this->num_per_batch)) { $this->num_per_batch = 1000000; } $output_speed_stats = Config::get('get_speed_stats', false) !== false; /** * @var $campaign Campaign */ foreach ($campaigns as $campaign) { $this->current_campaign = $campaign; $this->counters['campaign']++; $this->failed_sent = 0; $throttlecount = 0; $this->counters['total_subscribers_for_campaign ' . $campaign->id] = 0; $this->counters['processed_subscribers_for_campaign ' . $campaign->id] = 0; if ($output_speed_stats) { phpList::log()->debug('start send ' . $campaign->id, ['page' => 'porcessqueue']); } /* * TODO: enable plugins foreach ($GLOBALS['plugins'] as $pluginname => $plugin) { $plugin->campaignStarted($msgdata); }*/ if ($campaign->resetstats == 1) { $campaign->resetCampaignStatistics(); ## make sure to reset the resetstats flag, so it doesn't clear it every run $campaign->setDataItem('resetstats', 0); } ## check the end date of the campaign //if (!empty($campaign->'finishsending')) { $finish_sending_before = $campaign->finishsending->getTimestamp(); $seconds_to_go = $finish_sending_before - time(); $stop_sending = $seconds_to_go < 0; if (!$this->reload) { ### Hmm, this is probably incredibly confusing. It won't finish then if (Config::VERBOSE) { phpList::log()->info(sprintf(s('sending of this campaign will stop, if it is still going in %s'), Util::secs2time($seconds_to_go))); } } //} $subscriberselection = $campaign->subscriberselection; ## @@ needs more work ## load campaign in cache if (!PrepareCampaign::precacheCampaign($campaign)) { ## precache may fail on eg invalid remote URL ## any reporting needed here? # mark the campaign as suspended phpList::DB()->query(sprintf('UPDATE %s SET status = "suspended" WHERE id = %d', Config::getTableName('message'), $campaign->id)); phpList::log()->debug(s('Error loading campaign, please check the eventlog for details'), ['page' => 'porcessqueue']); if (Config::MANUALLY_PROCESS_QUEUE) { # wait a little, otherwise the campaign won't show sleep(10); } continue; } if ($output_speed_stats) { phpList::log()->debug('campaign data loaded ', ['page' => 'porcessqueue']); } //if (Config::VERBOSE) { // phpList::log()->debug($msgdata, ['page' => 'porcessqueue']); //} if (!empty($campaign->notify_start) && !isset($campaign->start_notified)) { $notifications = explode(',', $campaign->notify_start); foreach ($notifications as $notification) { phpListMailer::sendMail($notification, s('Campaign started'), sprintf(s('phplist has started sending the campaign with subject %s'), $campaign->subject . "\n\n" . sprintf(s('to view the progress of this campaign, go to http://%s'), Config::get('website') . Config::get('adminpages') . '/?page=campaigns&tab=active'))); } $campaign->setDataItem('start_notified', 'CURRENT_TIMESTAMP'); } if (!$this->reload) { phpList::log()->debug(s('Processing campaign') . ' ' . $campaign->id, ['page' => 'porcessqueue']); } flush(); Process::keepLock($this->send_process_id); $campaign->setStatus('inprocess'); if (!$this->reload) { phpList::log()->debug(s('Looking for subscribers'), ['page' => 'porcessqueue']); } if (phpList::DB()->hasError()) { $this->queueProcessError(phpList::DB()->error()); } # make selection on attribute, subscribers who at least apply to the attributes # lots of ppl seem to use it as a normal mailinglist system, and do not use attributes. # Check this and take anyone in that case. ## keep an eye on how long it takes to find subscribers, and warn if it's a long time $find_subscriber_start = Timer::get('process_queue')->elapsed(true); $attribute_count = phpList::DB()->query(sprintf('SELECT COUNT(*) FROM %s', Config::getTableName('attribute')))->fetchColumn(0); $subscriber_attribute_query = ''; #16552 if ($subscriberselection && $attribute_count) { $result = phpList::DB()->query($subscriberselection); $this->counters['total_subscribers_for_campaign'] = $result->rowCount(); if (!$this->reload) { phpList::log()->info($this->counters['total_subscribers_for_campaign'] . ' ' . s('subscribers apply for attributes, now checking lists'), ['page' => 'processqueue']); } $subscriber_list = ''; while ($fetched_subscriber = $result->fetchColumn(0)) { $subscriber_list .= $fetched_subscriber . ","; } $subscriber_list = substr($subscriber_list, 0, -1); if ($subscriber_list) { $subscriber_attribute_query = " AND listuser.userid IN ({$subscriber_list})"; } else { if (!$this->reload) { phpList::log()->debug(s('No subscribers apply for attributes'), ['page' => 'porcessqueue']); } $campaign->setStatus('sent'); //finish("info", "Campaign $campaignid: \nNo subscribers apply for attributes, ie nothing to do"); $subject = s("Maillist Processing info"); if (!$this->nothingtodo) { phpList::log()->debug(s('Finished this run'), ['page' => 'porcessqueue']); phpList::log()->info(s('%s of %s done', $this->sent, $this->counters['total_subscribers_for_campaign ' . $this->current_campaign->id]), ['page' => 'progress']); } //TODO:enable plugins /* if (!Config::TEST && !$this->nothingtodo && Config::get(('END_QUEUE_PROCESSING_REPORT'))) { foreach ($GLOBALS['plugins'] as $pluginname => $plugin) { $plugin->sendReport($subject,"Campaign $campaign->id: \nNo subscribers apply for attributes, ie nothing to do"); } } */ $this->script_stage = 6; # we should actually continue with the next campaign return true; } } if ($this->script_stage < 3) { $this->script_stage = 3; # we know the subscribers by attribute } # when using commandline we need to exclude subscribers who have already received # the email # we don't do this otherwise because it slows down the process, possibly # causing us to not find anything at all $exclusion = ''; /*$donesubscribers = array(); $skipsubscribers = array(); # 8478, avoid building large array in memory, when sending large amounts of subscribers. $result = Sql_Query("select userid from {$tables['usermessage']} where messageid = $campaignid"); $skipped = Sql_Affected_Rows(); if ($skipped < 10000) { while ($row = Sql_Fetch_Row($result)) { $alive = checkLock($this->send_process_id); if ($alive) keepLock($this->send_process_id); else ProcessError(s('Process Killed by other process')); array_push($donesubscribers,$row[0]); } } else { phpList::log()->debug(s('Warning, disabling exclusion of done subscribers, too many found'), ['page' => 'porcessqueue']); logEvent(s('Warning, disabling exclusion of done subscribers, too many found')); } # also exclude unconfirmed subscribers, otherwise they'll block the process # will give quite different statistics than when used web based # $result = Sql_Query("select id from {$tables['user']} where !confirmed"); # while ($row = Sql_Fetch_Row($result)) { # array_push($donesubscribers,$row[0]); # } if (sizeof($donesubscribers)) $exclusion = " and listuser.userid not in (".join(",",$donesubscribers).")"; */ if (Config::USE_LIST_EXCLUDE) { if (Config::VERBOSE) { phpList::log()->debug(s('looking for subscribers who can be excluded from this mailing'), ['page' => 'porcessqueue']); } //TODO: change this so it happens automatically when set in the campaign object if (!empty($campaign->excludelist)) { $campaign->excludeSubscribersOnList($campaign->excludelist); /*if (Config::VERBOSE) { phpList::log()->debug('Exclude query ' . phpList::DB()->getLastQuery(), ['page' => 'porcessqueue']); }*/ } } /* ## 8478 $subscriberids_query = sprintf('select distinct user.id from %s as listuser, %s as user, %s as listmessage where listmessage.messageid = %d and listmessage.listid = listuser.listid and user.id = listuser.userid %s %s %s', $tables['listuser'],$tables['user'],$tables['listmessage'], $campaignid, $subscriberconfirmed, $exclusion, $subscriber_attribute_query);*/ $queued = 0; if (Config::MESSAGEQUEUE_PREPARE) { $subscriberids_query = sprintf('SELECT userid FROM %s WHERE messageid = %d AND status = "todo"', Config::getTableName('usermessage'), $campaign->id); $subscriberids_result = phpList::DB()->query($subscriberids_query)->rowCount(); # if (Config::VERBOSE) { phpList::log()->info('found pre-queued subscribers ' . $subscriberids_result, ['page' => 'processqueue']); } ## if the above didn't find any, run the normal search (again) if ($subscriberids_result <= 0) { ## remove pre-queued campaigns, otherwise they wouldn't go out $removed = phpList::DB()->query(sprintf('DELETE FROM %s WHERE messageid = %d AND status = "todo"', Config::getTableName('usermessage'), $campaign->id))->rowCount(); if ($removed > 0) { phpList::log()->info('removed pre-queued subscribers ' . $removed, ['page' => 'processqueue']); } $subscriberids_query = sprintf('SELECT DISTINCT user.id FROM %s AS listuser CROSS JOIN %s AS user CROSS JOIN %s AS listmessage LEFT JOIN %s AS user_message ON (user_message.messageid = %d AND user_message.userid = listuser.userid) WHERE true AND listmessage.messageid = %d AND listmessage.listid = listuser.listid AND user.id = listuser.userid AND user_message.userid IS NULL AND user.confirmed AND !user.blacklisted AND !user.disabled %s %s', Config::getTableName('listuser'), Config::getTableName('user', true), Config::getTableName('listmessage'), Config::getTableName('usermessage'), $exclusion, $subscriber_attribute_query); $subscriberids_result = phpList::DB()->query($subscriberids_query); } if (Config::VERBOSE) { phpList::log()->debug('Subscriber select query ' . $subscriberids_query, ['page' => 'porcessqueue']); } if (phpList::DB()->hasError()) { $this->queueProcessError(phpList::DB()->error()); } # now we have all our subscribers to send the campaign to $this->counters['total_subscribers_for_campaign ' . $campaign->id] = $subscriberids_result->rowCount(); /*if ($skipped >= 10000) { $this->counters['total_subscribers_for_campaign ' . $campaign->id] -= $skipped; }*/ $find_subscriber_end = Timer::get('process_queue')->elapsed(true); if ($find_subscriber_end - $find_subscriber_start > 300 && $commandline) { phpList::log()->info(s('Warning, finding the subscribers to send out to takes a long time, consider changing to commandline sending')); } if (!$this->reload) { phpList::log()->info(s('Found them') . ': ' . $this->counters['total_subscribers_for_campaign ' . $campaign->id] . ' ' . s('to process')); } $campaign->setDataItem('to process', $this->counters['total_subscribers_for_campaign ' . $campaign->id]); if (Config::MESSAGEQUEUE_PREPARE) { ## experimental MESSAGEQUEUE_PREPARE will first mark all campaigns as todo and then work it's way through the todo's ## that should save time when running the queue multiple times, which avoids the subscriber search after the first time ## only do this first time, ie empty($queued); ## the last run will pick up changes while ($fetched_subscriber_id = $subscriberids_result->fetchColumn(0)) { ## mark campaign/subscriber combination as "todo" $campaign->updateSubscriberCampaignStatus($fetched_subscriber_id, 'todo'); } ## rerun the initial query, in order to continue as normal $subscriberids_query = sprintf('SELECT userid FROM %s WHERE messageid = %d AND status = "todo"', Config::getTableName('usermessage'), $campaign->id); $subscriberids_result = phpList::DB()->query($subscriberids_query); $this->counters['total_subscribers_for_campaign ' . $campaign->id] = $subscriberids_result->rowCount(); } if (Config::MAILQUEUE_BATCH_SIZE > 0) { ## in case of sending multiple campaigns, reduce batch with "sent" $this->num_per_batch -= $this->sent; # send in batches of $this->num_per_batch subscribers $batch_total = $this->counters['total_subscribers_for_campaign ' . $campaign->id]; if ($this->num_per_batch > 0) { $subscriberids_query .= sprintf(' LIMIT 0,%d', $this->num_per_batch); if (Config::VERBOSE) { phpList::log()->debug($this->num_per_batch . ' query -> ' . $subscriberids_query, ['page' => 'porcessqueue']); } try { $subscriberids_result = phpList::DB()->query($subscriberids_query); } catch (\PDOException $e) { $this->queueProcessError($e->getMessage()); } } else { phpList::log()->debug(s('No subscribers to process for this batch'), ['page' => 'porcessqueue']); //TODO: Can we remove this pointless query (will have to change the while loop below) $subscriberids_result = phpList::DB()->query(sprintf('SELECT * FROM %s WHERE id = 0', Config::getTableName('user'))); } $affrows = $subscriberids_result->rowCount(); phpList::log()->debug(s('Processing batch of ') . ': ' . $affrows, ['page' => 'porcessqueue']); } while ($subscriberdata = $subscriberids_result->fetch()) { $this->counters['processed_subscribers_for_campaign ' . $campaign->id]++; $failure_reason = ''; if ($this->num_per_batch && $this->sent >= $this->num_per_batch) { phpList::log()->debug(s('batch limit reached') . ": {$this->sent} ({$this->num_per_batch})", ['page' => 'porcessqueue']); Config::setRunningConfig('wait', $this->batch_period); return false; } $subscriber = Subscriber::getSubscriber($subscriberdata[0]); # id of the subscriber if ($output_speed_stats) { phpList::log()->info('-----------------------------------' . "\n" . 'start process subscriber ' . $subscriber->id); } $some = 1; set_time_limit(120); $seconds_to_go = $finish_sending_before - time(); $stop_sending = $seconds_to_go < 0; # check if we have been "killed" # phpList::log()->debug('Process ID '.$this->send_process_id, ['page' => 'porcessqueue']); $alive = Process::checkLock($this->send_process_id); ## check for max-process-queue-time $elapsed = Timer::get('process_queue')->elapsed(true); if ($restrictions['max_process_queue_time'] && $elapsed > $restrictions['max_process_queue_time'] && $this->sent > 0) { phpList::log()->info(s('queue processing time has exceeded max processing time ') . $restrictions['max_process_queue_time'], ['page' => 'processqueue']); break; } elseif ($alive && !$stop_sending) { Process::keepLock($this->send_process_id); } elseif ($stop_sending) { phpList::log()->debug(s('Campaign sending timed out, is past date to process until'), ['page' => 'porcessqueue']); break; } else { $this->queueProcessError(s('Process Killed by other process')); } # check if the campaign we are working on is still there and in process $campaign = Campaign::getCampaign($campaign->id); if (empty($campaign)) { $this->queueProcessError(s('Campaign I was working on has disappeared')); } elseif ($campaign->status != 'inprocess') { $this->queueProcessError(s('Sending of this campaign has been suspended')); } flush(); ## #Sql_Query_Params(sprintf('delete from %s where userid = ? and messageid = ? and status = "active"',$tables['usermessage']), array($subscriberid,$campaign->id)); # check whether the subscriber has already received the campaign if ($output_speed_stats) { phpList::log()->debug('verify campaign can go out to ' . $subscriber->id, ['page' => 'porcessqueue']); } $um = phpList::DB()->query(sprintf('SELECT entered FROM %s WHERE userid = %d AND messageid = %d AND status != "todo"', Config::getTableName('usermessage'), $subscriber->id, $campaign->id)); if ($um->rowCount() <= 0) { ## mark this campaign that we're working on it, so that no other process will take it ## between two lines ago and here, should hopefully be quick enough $campaign->updateSubscriberCampaignStatus($subscriber->id, 'active'); //TODO: could this work to make sure no other process is already sending this email? /*if(phpList::DB()->affectedRows() == 0){ break; }*/ if ($this->script_stage < 4) { $this->script_stage = 4; # we know a subscriber to send to } $somesubscribers = 1; # pick the first one (rather historical from before email was unique) //TODO: since we don't allow invalid email addresses to be set, can we omit this check // or is there a reason to keep it here? if ($subscriber->confirmed && Validation::isEmail($subscriber->getEmailAddress())) { /* ## Ask plugins if they are ok with sending this campaign to this subscriber */ /*TODO: enable plugins if ($output_speed_stats){ phpList::log()->debug('start check plugins ', ['page' => 'porcessqueue']); } reset($GLOBALS['plugins']); while ($cansend && $plugin = current($GLOBALS['plugins'])) { if (Config::VERBOSE) { cl_output('Checking plugin ' . $plugin->name()); } $cansend = $plugin->canSend($campaign, $subscriber); if (!$cansend) { $failure_reason .= 'Sending blocked by plugin ' . $plugin->name; $this->counters['send blocked by ' . $plugin->name]++; if (Config::VERBOSE) { cl_output('Sending blocked by plugin ' . $plugin->name); } } next($GLOBALS['plugins']); } if ($output_speed_stats){ phpList::log()->debug('end check plugins ', ['page' => 'porcessqueue']); }*/ #################################### # Throttling $throttled = 0; if ($subscriber->allowsReceivingMails() && Config::USE_DOMAIN_THROTTLE) { //TODO: what if we were to put the domain name of the email address in a separate database field //we could even query for the domain then list($mailbox, $domainname) = explode('@', $subscriber->getEmailAddress()); $now = time(); $interval = $now - $now % Config::DOMAIN_BATCH_PERIOD; if (!isset($this->domainthrottle[$domainname]) || !is_array($this->domainthrottle[$domainname])) { $this->domainthrottle[$domainname] = array('interval' => '', 'sent' => 0, 'attempted' => 0); } elseif (isset($this->domainthrottle[$domainname]['interval']) && $this->domainthrottle[$domainname]['interval'] == $interval) { $throttled = $this->domainthrottle[$domainname]['sent'] >= Config::DOMAIN_BATCH_SIZE; if ($throttled) { $this->counters['send blocked by domain throttle']++; $this->domainthrottle[$domainname]['attempted']++; if (Config::DOMAIN_AUTO_THROTTLE && $this->domainthrottle[$domainname]['attempted'] > 25 && $num_campaigns <= 1 && $this->counters['total_subscribers_for_campaign ' . $campaign->id] < 1000) { $this->domainthrottle[$domainname]['attempted'] = 0; phpList::log()->notice(sprintf(s('There have been more than 10 attempts to send to %s that have been blocked for domain throttling.'), $domainname)); phpList::log()->notice(s('Introducing extra delay to decrease throttle failures')); if (Config::VERBOSE) { phpList::log()->info(s('Introducing extra delay to decrease throttle failures')); } if (!isset($running_throttle_delay)) { $running_throttle_delay = (int) (Config::MAILQUEUE_THROTTLE + Config::DOMAIN_BATCH_PERIOD / (Config::DOMAIN_BATCH_SIZE * 4)); } else { $running_throttle_delay += (int) (Config::DOMAIN_BATCH_PERIOD / (Config::DOMAIN_BATCH_SIZE * 4)); } #phpList::log()->debug("Running throttle delay: ".$running_throttle_delay, ['page' => 'porcessqueue']); } elseif (Config::VERBOSE) { phpList::log()->info(sprintf(s('%s is currently over throttle limit of %d per %d seconds') . ' (' . $this->domainthrottle[$domainname]['sent'] . ')', $domainname, Config::DOMAIN_BATCH_SIZE, Config::DOMAIN_BATCH_PERIOD)); } } } } if ($subscriber->allowsReceivingMails()) { $success = false; if (Config::TEST) { $success = $this->sendEmailTest($campaign->id, $subscriber->getEmailAddress()); } else { /*TODO: enable plugins reset($GLOBALS['plugins']); while (!$throttled && $plugin = current($GLOBALS['plugins'])) { $throttled = $plugin->throttleSend($msgdata, $subscriber); if ($throttled) { if (!isset($this->counters['send throttled by plugin ' . $plugin->name])) { $this->counters['send throttled by plugin ' . $plugin->name] = 0; } $this->counters['send throttled by plugin ' . $plugin->name]++; $failure_reason .= 'Sending throttled by plugin ' . $plugin->name; } next($GLOBALS['plugins']); } */ if (!$throttled) { if (Config::VERBOSE) { phpList::log()->info(s('Sending') . ' ' . $campaign->id . ' ' . s('to') . ' ' . $subscriber->getEmailAddress()); } Timer::start('email_sent_timer'); $this->counters['batch_count']++; $success = PrepareCampaign::sendEmail($campaign, $subscriber); if (!$success) { $this->counters['sendemail returned false']++; } if (Config::VERBOSE) { phpList::log()->info(s('It took') . ' ' . Timer::get('email_sent_timer')->elapsed(true) . ' ' . s('seconds to send')); } } else { $throttlecount++; } } ############################# # tried to send email , process succes / failure if ($success) { if (Config::USE_DOMAIN_THROTTLE) { list($mailbox, $domainname) = explode('@', $subscriber->getEmailAddress()); if ($this->domainthrottle[$domainname]['interval'] != $interval) { $this->domainthrottle[$domainname]['interval'] = $interval; $this->domainthrottle[$domainname]['sent'] = 0; } else { $this->domainthrottle[$domainname]['sent']++; } } $this->sent++; $campaign->updateSubscriberCampaignStatus($subscriber->id, 'sent'); } else { $this->failed_sent++; ## need to check this, the entry shouldn't be there in the first place, so no need to delete it ## might be a cause for duplicated emails if (Config::MESSAGEQUEUE_PREPARE) { phpList::DB()->query(sprintf('UPDATE %s SET status = "todo" WHERE userid = %d AND messageid = %d AND status = "active"', Config::getTableName('usermessage'), $subscriber->id, $campaign->id)); } else { phpList::DB()->query(sprintf('DELETE FROM %s WHERE userid = %d AND messageid = %d AND status = "active"', Config::getTableName('usermessage'), $subscriber->id, $campaign->id)); } if (Config::VERBOSE) { phpList::log()->debug(s('Failed sending to') . ' ' . $subscriber->getEmailAddress(), ['page' => 'porcessqueue']); phpList::log()->notice(sprintf('Failed sending campaign %d to %s', $campaign->id, $subscriber->getEmailAddress())); } # make sure it's not because it's an underdeliverable email # unconfirm this subscriber, so they're not included next time //TODO: since we don't allow invalid email addresses to be set, can we omit this check // or is there a reason to keep it here? if (!$throttled && !Validation::validateEmail($subscriber->getEmailAddress())) { $this->unconfirmed++; $this->counters['email address invalidated']++; phpList::log()->notice(sprintf('invalid email address %s subscriber marked unconfirmed', $subscriber->getEmailAddress())); $subscriber->confirmed = false; $subscriber->save(); /*Sql_Query( sprintf( 'update %s set confirmed = 0 where email = "%s"', $GLOBALS['tables']['user'], $subscriberemail ) );*/ } } if ($this->script_stage < 5) { $this->script_stage = 5; # we have actually sent one subscriber } if (isset($running_throttle_delay)) { sleep($running_throttle_delay); if ($this->sent % 5 == 0) { # retry running faster after some more campaigns, to see if that helps unset($running_throttle_delay); } } elseif (Config::MAILQUEUE_THROTTLE) { usleep(Config::MAILQUEUE_THROTTLE * 1000000); } elseif (Config::MAILQUEUE_BATCH_SIZE && Config::MAILQUEUE_AUTOTHROTTLE) { $totaltime = Timer::get('process_queue')->elapsed(true); //$msgperhour = (3600 / $totaltime) * $this->sent; //$msgpersec = $msgperhour / 3600; ##11336 - this may cause "division by 0", but 'secpermsg' isn't used at all # $secpermsg = $totaltime / $this->sent; $target = Config::MAILQUEUE_BATCH_PERIOD / Config::MAILQUEUE_BATCH_SIZE * $this->sent; $delay = $target - $totaltime; #phpList::log()->debug("Sent: $this->sent mph $msgperhour mps $msgpersec secpm $secpermsg target $target actual $actual d $delay", ['page' => 'porcessqueue']); if ($delay > 0) { if (Config::VERBOSE) { /* phpList::log()->info(s('waiting for').' '.$delay.' '.s('seconds').' '. s('to make sure we don\'t exceed our limit of ').MAILQUEUE_BATCH_SIZE.' '. s('campaigns in ').' '.MAILQUEUE_BATCH_PERIOD.s('seconds')); */ phpList::log()->info(sprintf(s('waiting for %.1f seconds to meet target of %s seconds per campaign'), $delay, Config::MAILQUEUE_BATCH_PERIOD / Config::MAILQUEUE_BATCH_SIZE)); } usleep($delay * 1000000); } } } else { $this->cannotsend++; # mark it as sent anyway, because otherwise the process will never finish if (Config::VERBOSE) { phpList::log()->debug(s('not sending to ') . $subscriber->getEmailAddress(), ['page' => 'porcessqueue']); } $campaign->updateSubscriberCampaignStatus($subscriber->id, 'not sent'); } # update possible other subscribers matching this email as well, # to avoid duplicate sending when people have subscribed multiple times # bit of legacy code after making email unique in the database #$emails = Sql_query("select * from {$tables['user']} where email =\"$subscriberemail\""); #while ($email = Sql_fetch_row($emails)) #Sql_query("replace into {$tables['usermessage']} (userid,messageid) values($email[0],$campaign->id)"); } else { # some "invalid emails" are entirely empty, ah, that is because they are unconfirmed ## this is quite old as well, with the preselection that avoids unconfirmed subscribers # it is unlikely this is every processed. if (!$subscriber->confirmed || $subscriber->disabled) { if (Config::VERBOSE) { phpList::log()->info(s('Unconfirmed subscriber') . ': ' . $subscriber->id . ' ' . $subscriber->getEmailAddress() . ' ' . $subscriber->id); } $this->unconfirmed++; # when running from commandline we mark it as sent, otherwise we might get # stuck when using batch processing # if ($GLOBALS['commandline']) { $campaign->updateSubscriberCampaignStatus($subscriber->id, 'unconfirmed subscriber'); # } //TODO: can probably remove below check } elseif ($subscriber->getEmailAddress() || $subscriber->id) { if (Config::VERBOSE) { phpList::log()->debug(s('Invalid email address') . ': ' . $subscriber->getEmailAddress() . ' ' . $subscriber->id, ['page' => 'porcessqueue']); } phpList::log()->notice(s('Invalid email address') . ': userid ' . $subscriber->id . ' email ' . $subscriber->getEmailAddress()); # mark it as sent anyway if ($subscriber->id > 0) { $campaign->updateSubscriberCampaignStatus($subscriber->id, 'invalid email address'); $subscriber->confirmed = 0; $subscriber->save(); $subscriber->addHistory(s('Subscriber marked unconfirmed for invalid email address'), s('Marked unconfirmed while sending campaign %d', $campaign->id), $campaign->id); } $this->invalid++; } } } else { //TODO: remove below ## and this is quite historical, and also unlikely to be every called # because we now exclude subscribers who have received the campaign from the # query to find subscribers to send to ## when trying to send the campaign, it was already marked for this subscriber ## June 2010, with the multiple send process extension, that's quite possible to happen again $um = $um->fetch(); $this->notsent++; if (Config::VERBOSE) { phpList::log()->info(s('Not sending to') . ' ' . $subscriber->id . ', ' . s('already sent') . ' ' . $um[0]); } } $campaign->incrementProcessedAmount(); $this->processed = $this->notsent + $this->sent + $this->invalid + $this->unconfirmed + $this->cannotsend + $this->failed_sent; #if ($this->processed % 10 == 0) { if (0) { phpList::log()->info('AR' . $affrows . ' N ' . $this->counters['total_subscribers_for_campaign ' . $campaign->id] . ' P' . $this->processed . ' S' . $this->sent . ' N' . $this->notsent . ' I' . $this->invalid . ' U' . $this->unconfirmed . ' C' . $this->cannotsend . ' F' . $this->failed_sent); $rn = $reload * $this->num_per_batch; phpList::log()->info('P ' . $this->processed . ' N' . $this->counters['total_subscribers_for_campaign ' . $campaign->id] . ' NB' . $this->num_per_batch . ' BT' . $batch_total . ' R' . $reload . ' RN' . $rn); } /* * don't calculate this here, but in the "msgstatus" instead, so that * the total speed can be calculated, eg when there are multiple send processes * * re-added for commandline outputting */ $totaltime = Timer::get('process_queue')->elapsed(true); if ($this->sent > 0) { $msgperhour = 3600 / $totaltime * $this->sent; $secpermsg = $totaltime / $this->sent; $timeleft = ($this->counters['total_subscribers_for_campaign ' . $campaign->id] - $this->sent) * $secpermsg; $eta = date('D j M H:i', time() + $timeleft); } else { $msgperhour = 0; $secpermsg = 0; $timeleft = 0; $eta = s('unknown'); } $campaign->setDataItem('ETA', $eta); $campaign->setDataItem('msg/hr', $msgperhour); cl_progress('sent ' . $this->sent . ' ETA ' . $eta . ' sending ' . sprintf('%d', $msgperhour) . ' msg/hr'); $campaign->setDataItem('to process', $this->counters['total_subscribers_for_campaign ' . $campaign->id] - $this->sent); $campaign->setDataItem('last msg sent', time()); #$campaign->setDataItem('totaltime', $this->timer->elapsed(true)); if ($output_speed_stats) { phpList::log()->info('end process subscriber ' . "\n" . '-----------------------------------' . "\n" . $subscriber->id); } } $this->processed = $this->notsent + $this->sent + $this->invalid + $this->unconfirmed + $this->cannotsend + $this->failed_sent; phpList::log()->info(s('Processed %d out of %d subscribers', $this->counters['processed_subscribers_for_campaign ' . $campaign->id], $this->counters['total_subscribers_for_campaign ' . $campaign->id]), ['page' => 'processqueue']); if ($this->counters['total_subscribers_for_campaign ' . $campaign->id] - $this->sent <= 0 || $stop_sending) { # this campaign is done if (!$somesubscribers) { phpList::log()->debug(s('Hmmm, No subscribers found to send to'), ['page' => 'porcessqueue']); } if (!$this->failed_sent) { $campaign->repeatCampaign(); $campaign->setStatus('sent'); if (!empty($campaign->notify_end) && !isset($campaign->end_notified)) { $notifications = explode(',', $campaign->notify_end); foreach ($notifications as $notification) { phpListMailer::sendMail($notification, s('Campaign campaign finished'), sprintf(s('phpList has finished sending the campaign with subject %s'), $campaign->subject) . "\n\n" . sprintf(s('to view the results of this campaign, go to http://%s'), Config::get('website') . Config::get('adminpages') . '/?page=statsoverview&id=' . $campaign->id)); } $campaign->setDataItem('end_notified', 'CURRENT_TIMESTAMP'); } /*TODO: Do we need to refetch these values from db? * $query = " select sent, sendstart" . " from ${tables['message']}" . " where id = ?"; $rs = Sql_Query_Params($query, array($campaign->id)); $timetaken = Sql_Fetch_Row($rs);*/ phpList::log()->debug(s('It took') . ' ' . Util::timeDiff($campaign->sent, $campaign->sendstart) . ' ' . s('to send this campaign')); $this->sendCampaignStats($campaign); } $cache = Cache::instance(); ## flush cached campaign track stats to the DB if (isset($cache->linktrack_sent_cache[$campaign->id])) { Cache::flushClicktrackCache(); # we're done with $campaign->id, so get rid of the cache unset($cache->linktrack_sent_cache[$campaign->id]); } } else { if ($this->script_stage < 5) { $this->script_stage = 5; } } } if (!$num_campaigns) { $this->script_stage = 6; # we are done } # shutdown will take care of reporting return true; }