Exemple #1
0
 /**
  * @param Campaign $campaign
  * @param Subscriber $subscriber
  * @param bool $is_test_mail
  * @param array $forwardedby
  * @return bool
  */
 public static function sendEmail($campaign, $subscriber, $is_test_mail = false, $forwardedby = array())
 {
     $get_speed_stats = Config::VERBOSE && Config::get('getspeedstats', false) !== false && Timer::get('process_queue') != null;
     $sql_count_start = phpList::DB()->getQueryCount();
     ## for testing concurrency, put in a delay to check if multiple send processes cause duplicates
     #usleep(rand(0,10) * 1000000);
     if ($get_speed_stats) {
         phpList::log()->debug('sendEmail start ' . Timer::get('process_queue')->interval(1), ['page' => 'preparecampaign']);
     }
     #0013076: different content when forwarding 'to a friend'
     if (Config::FORWARD_ALTERNATIVE_CONTENT) {
         $forwardContent = sizeof($forwardedby) > 0;
     } else {
         $forwardContent = 0;
     }
     if (!Cache::isCampaignCached($campaign)) {
         if (!PrepareCampaign::precacheCampaign($campaign, $forwardContent)) {
             phpList::log()->notice('Error loading campaign ' . $campaign->id . '  in cache');
             return false;
         }
     } else {
         #  dbg("Using cached {$cached->fromemail}");
         if (Config::VERBOSE) {
             phpList::log()->debug('Using cached campaign', ['page' => 'preparecampaign']);
         }
     }
     $cached_campaign = Cache::getCachedCampaign($campaign);
     if (Config::VERBOSE) {
         phpList::log()->debug(s('Sending campaign %d with subject %s to %s', $campaign->id, $cached_campaign->subject, $subscriber->getEmailAddress()), ['page' => 'preparecampaign']);
     }
     ## at this stage we don't know whether the content is HTML or text, it's just content
     $content = $cached_campaign->content;
     if ($get_speed_stats) {
         phpList::log()->debug('Load subscriber start', ['page' => 'preparecampaign']);
     }
     #0011857: forward to friend, retain attributes
     if ($subscriber->uniqid == 'forwarded' && Config::KEEPFORWARDERATTRIBUTES) {
         $subscriber = Subscriber::getSubscriberByEmailAddress($forwardedby['email']);
     }
     $subscriber_att_values = $subscriber->getCleanAttributes();
     $html = $text = array();
     if (stripos($content, '[LISTS]') !== false) {
         $lists = MailingList::getListsForSubscriber($subscriber->id);
         if (!empty($lists)) {
             foreach ($lists as $list) {
                 $html['lists'] .= '<br/>' . $list->name;
                 $text['lists'] .= "\n" . $list->name;
             }
         } else {
             $html['lists'] = s('strNoListsFound');
             $text['lists'] = s('strNoListsFound');
         }
     }
     if ($get_speed_stats) {
         phpList::log()->debug('Load subscriber end', ['page' => 'preparecampaign']);
     }
     if ($cached_campaign->subscriberpecific_url) {
         if ($get_speed_stats) {
             phpList::log()->debug('fetch personal URL start', ['page' => 'preparecampaign']);
         }
         ## Fetch external content, only if the URL has placeholders
         //TODO: I changed can_fetchUrl to can_fetch_url -> make sure it's changed everywhere
         if (Config::get('can_fetch_url') && preg_match('/\\[URL:([^\\s]+)\\]/i', $content, $regs)) {
             while (isset($regs[1]) && strlen($regs[1])) {
                 $url = $regs[1];
                 if (!preg_match('/^http/i', $url)) {
                     $url = 'http://' . $url;
                 }
                 $remote_content = Util::fetchUrl($url, $subscriber);
                 # @@ don't use this
                 #      $remote_content = includeStyles($remote_content);
                 if ($remote_content) {
                     $content = str_replace($regs[0], $remote_content, $content);
                     $cached_campaign->htmlformatted = strip_tags($content) != $content;
                 } else {
                     phpList::log()->notice('Error fetching URL: ' . $regs[1] . ' to send to ' . $subscriber->getEmailAddress());
                     return 0;
                 }
                 preg_match('/\\[URL:([^\\s]+)\\]/i', $content, $regs);
             }
         }
         if ($get_speed_stats) {
             phpList::log()->debug('fetch personal URL end', ['page' => 'preparecampaign']);
         }
     }
     if ($get_speed_stats) {
         phpList::log()->debug('define placeholders start', ['page' => 'preparecampaign']);
     }
     //TODO: can't we precache parts of the urls and the just use string concatenation for better performance
     $unsubscribe_url = Config::get('unsubscribeurl');
     ## https://mantis.phplist.com/view.php?id=16680 -> the "sep" should be & for the text links
     $sep = strpos($unsubscribe_url, '?') === false ? '?' : '&';
     $html['unsubscribe'] = sprintf('<a href="%s%suid=%s">%s</a>', $unsubscribe_url, htmlspecialchars($sep), $subscriber->uniqid, s('Unsubscribe'));
     $text['unsubscribe'] = sprintf('%s%suid=%s', $unsubscribe_url, $sep, $subscriber->uniqid);
     $text['jumpoff'] = sprintf('%s%suid=%s&jo=1', $unsubscribe_url, $sep, $subscriber->uniqid);
     $html['unsubscribeurl'] = sprintf('%s%suid=%s', $unsubscribe_url, htmlspecialchars($sep), $subscriber->uniqid);
     $text['unsubscribeurl'] = sprintf('%s%suid=%s', $unsubscribe_url, $sep, $subscriber->uniqid);
     $text['jumpoffurl'] = sprintf('%s%suid=%s&jo=1', $unsubscribe_url, $sep, $subscriber->uniqid);
     #0013076: Blacklisting posibility for unknown subscribers
     $blacklist_url = Config::get('blacklisturl');
     $sep = strpos($blacklist_url, '?') === false ? '?' : '&';
     $html['blacklist'] = sprintf('<a href="%s%semail=%s">%s</a>', $blacklist_url, htmlspecialchars($sep), $subscriber->getEmailAddress(), s('Unsubscribe'));
     $text['blacklist'] = sprintf('%s%semail=%s', $blacklist_url, $sep, $subscriber->getEmailAddress());
     $html['blacklisturl'] = sprintf('%s%semail=%s', $blacklist_url, htmlspecialchars($sep), $subscriber->getEmailAddress());
     $text['blacklisturl'] = sprintf('%s%semail=%s', $blacklist_url, $sep, $subscriber->getEmailAddress());
     #0013076: Problem found during testing: campaign part must be parsed correctly as well.
     if (sizeof($forwardedby) && isset($forwardedby['email'])) {
         $html['unsubscribe'] = $html['blacklist'];
         $text['unsubscribe'] = $text['blacklist'];
         $html['forwardedby'] = $forwardedby['email'];
         $text['forwardedby'] = $forwardedby['email'];
     }
     $subscribe_url = Config::get('subscribeurl');
     //$sep = strpos($subscribe_url, '?') === false ? '?' : '&';
     $html['subscribe'] = sprintf('<a href="%s">%s</a>', $subscribe_url, s('this link'));
     $text['subscribe'] = sprintf('%s', $subscribe_url);
     $html['subscribeurl'] = sprintf('%s', $subscribe_url);
     $text['subscribeurl'] = sprintf('%s', $subscribe_url);
     $forward_url = Config::get('forwardurl');
     $sep = strpos($forward_url, '?') === false ? '?' : '&';
     $html['forward'] = sprintf('<a href="%s%suid=%s&amp;mid=%d">%s</a>', $forward_url, htmlspecialchars($sep), $subscriber->uniqid, $campaign->id, s('this link'));
     $text['forward'] = sprintf('%s%suid=%s&mid=%d', $forward_url, $sep, $subscriber->uniqid, $campaign->id);
     $html['forwardurl'] = sprintf('%s%suid=%s&amp;mid=%d', $forward_url, htmlspecialchars($sep), $subscriber->uniqid, $campaign->id);
     $text['forwardurl'] = $text['forward'];
     $html['campaignid'] = $text['campaignid'] = sprintf('%d', $campaign->id);
     # make sure there are no newlines, otherwise they get turned into <br/>s
     $html['forwardform'] = sprintf('<form method="get" action="%s" name="forwardform" class="forwardform"><input type="hidden" name="uid" value="%s" /><input type="hidden" name="mid" value="%d" /><input type="hidden" name="p" value="forward" /><input type=text name="email" value="" class="forwardinput" /><input name="Send" type="submit" value="%s" class="forwardsubmit"/></form>', $forward_url, $subscriber->uniqid, $campaign->id, Config::get('strForward'));
     $text['signature'] = "\n\n-- powered by phpList, www.phplist.com --\n\n";
     $preferences_url = Config::get('preferencesurl');
     $sep = strpos($preferences_url, '?') === false ? '?' : '&';
     $html['preferences'] = sprintf('<a href="%s%suid=%s">%s</a>', $preferences_url, htmlspecialchars($sep), $subscriber->uniqid, s('this link'));
     $text['preferences'] = sprintf('%s%suid=%s', $preferences_url, $sep, $subscriber->uniqid);
     $html['preferencesurl'] = sprintf('%s%suid=%s', $preferences_url, htmlspecialchars($sep), $subscriber->uniqid);
     $text['preferencesurl'] = sprintf('%s%suid=%s', $preferences_url, $sep, $subscriber->uniqid);
     $confirmation_url = Config::get('confirmationurl');
     $sep = strpos($confirmation_url, '?') === false ? '?' : '&';
     $html['confirmationurl'] = sprintf('%s%suid=%s', $confirmation_url, htmlspecialchars($sep), $subscriber->uniqid);
     $text['confirmationurl'] = sprintf('%s%suid=%s', $confirmation_url, $sep, $subscriber->uniqid);
     #historical, not sure it's still used
     $html['userid'] = $text['userid'] = $subscriber->uniqid;
     $html['website'] = $text['website'] = Config::get('website');
     # Your website's address, e.g. www.yourdomain.com
     $html['domain'] = $text['domain'] = Config::get('domain');
     # Your domain, e.g. yourdomain.com
     if ($subscriber->uniqid != 'forwarded') {
         $text['footer'] = $cached_campaign->textfooter;
         $html['footer'] = $cached_campaign->htmlfooter;
     } else {
         #0013076: different content when forwarding 'to a friend'
         if (Config::FORWARD_ALTERNATIVE_CONTENT) {
             $text['footer'] = stripslashes($campaign->forwardfooter);
         } else {
             $text['footer'] = Config::get('forwardfooter');
         }
         $html['footer'] = $text['footer'];
     }
     /*
       We request you retain the signature below in your emails including the links.
       This not only gives respect to the large amount of time given freely
       by the developers  but also helps build interest, traffic and use of
       phpList, which is beneficial to it's future development.
     
       You can configure how the credits are added to your pages and emails in your
       config file.
     
       Michiel Dethmers, phpList Ltd 2003 - 2013
     */
     if (!Config::EMAILTEXTCREDITS) {
         $html['signature'] = Config::get('PoweredByImage');
         #'<div align="center" id="signature"><a href="http://www.phplist.com"><img src="powerphplist.png" width=88 height=31 title="Powered by PHPlist" alt="Powered by PHPlist" border="0" /></a></div>';
         # oops, accidentally became spyware, never intended that, so take it out again :-)
         $html['signature'] = preg_replace('/src=".*power-phplist.png"/', 'src="powerphplist.png"', $html['signature']);
     } else {
         $html['signature'] = Config::get('PoweredByText');
     }
     #  $content = $cached->htmlcontent;
     if ($get_speed_stats) {
         phpList::log()->debug('define placeholders end', ['page' => 'preparecampaign']);
     }
     ## Fill text and html versions depending on given versions.
     if ($get_speed_stats) {
         phpList::log()->debug('parse text to html or html to text start', ['page' => 'preparecampaign']);
     }
     if ($cached_campaign->htmlformatted) {
         if (empty($cached_campaign->textcontent)) {
             $textcontent = String::HTML2Text($content);
         } else {
             $textcontent = $cached_campaign->textcontent;
         }
         $htmlcontent = $content;
     } else {
         if (empty($cached_campaign->textcontent)) {
             $textcontent = $content;
         } else {
             $textcontent = $cached_campaign->textcontent;
         }
         $htmlcontent = PrepareCampaign::parseText($content);
     }
     if ($get_speed_stats) {
         phpList::log()->debug('parse text to html or html to text end', ['page' => 'preparecampaign']);
     }
     $defaultstyle = Config::get('html_email_style');
     $adddefaultstyle = 0;
     if ($get_speed_stats) {
         phpList::log()->debug('merge into template start', ['page' => 'preparecampaign']);
     }
     if ($cached_campaign->template) {
         # template used
         $htmlcampaign = str_replace('[CONTENT]', $htmlcontent, $cached_campaign->template);
     } else {
         # no template used
         $htmlcampaign = $htmlcontent;
         $adddefaultstyle = 1;
     }
     $textcampaign = $textcontent;
     if ($get_speed_stats) {
         phpList::log()->debug('merge into template end', ['page' => 'preparecampaign']);
     }
     ## Parse placeholders
     if ($get_speed_stats) {
         phpList::log()->debug('parse placeholders start', ['page' => 'preparecampaign']);
     }
     /*
       var_dump($html);
       var_dump($subscriberdata);
       var_dump($subscriber_att_values);
       exit;
     */
     #print htmlspecialchars($htmlcampaign);exit;
     ### @@@TODO don't use forward and forward form in a forwarded campaign as it'll fail
     if (strpos($htmlcampaign, '[FOOTER]') !== false) {
         $htmlcampaign = str_ireplace('[FOOTER]', $html['footer'], $htmlcampaign);
     } elseif ($html['footer']) {
         $htmlcampaign = PrepareCampaign::addHTMLFooter($htmlcampaign, '<br />' . $html['footer']);
     }
     if (strpos($htmlcampaign, '[SIGNATURE]') !== false) {
         $htmlcampaign = str_ireplace('[SIGNATURE]', $html['signature'], $htmlcampaign);
     } else {
         # BUGFIX 0015303, 2/2
         //    $htmlcampaign .= '<br />'.$html['signature'];
         $htmlcampaign = PrepareCampaign::addHTMLFooter($htmlcampaign, '
            ' . $html['signature']);
     }
     # END BUGFIX 0015303, 2/2
     if (strpos($textcampaign, '[FOOTER]')) {
         $textcampaign = str_ireplace('[FOOTER]', $text['footer'], $textcampaign);
     } else {
         $textcampaign .= "\n\n" . $text['footer'];
     }
     if (strpos($textcampaign, '[SIGNATURE]')) {
         $textcampaign = str_ireplace('[SIGNATURE]', $text['signature'], $textcampaign);
     } else {
         $textcampaign .= "\n" . $text['signature'];
     }
     ### addition to handle [FORWARDURL:Campaign ID:Link Text] (link text optional)
     while (preg_match('/\\[FORWARD:([^\\]]+)\\]/Uxm', $htmlcampaign, $regs)) {
         $newforward = $regs[1];
         $matchtext = $regs[0];
         if (strpos($newforward, ':')) {
             ## using FORWARDURL:campaignid:linktext
             list($forwardcampaign, $forwardtext) = explode(':', $newforward);
         } else {
             $forwardcampaign = sprintf('%d', $newforward);
             $forwardtext = 'this link';
         }
         if (!empty($forwardcampaign)) {
             $sep = strpos($forward_url, '?') === false ? '?' : '&';
             $forwardurl = sprintf('%s%suid=%s&mid=%d', $forward_url, $sep, $subscriber->uniqid, $forwardcampaign);
             $htmlcampaign = str_replace($matchtext, '<a href="' . htmlspecialchars($forwardurl) . '">' . $forwardtext . '</a>', $htmlcampaign);
         } else {
             ## make sure to remove the match, otherwise, it'll be an eternal loop
             $htmlcampaign = str_replace($matchtext, '', $htmlcampaign);
         }
     }
     ## the text campaign has to be parsed seperately, because the line might wrap if the text for the link is long, so the match text is different
     while (preg_match('/\\[FORWARD:([^\\]]+)\\]/Uxm', $textcampaign, $regs)) {
         $newforward = $regs[1];
         $matchtext = $regs[0];
         if (strpos($newforward, ':')) {
             ## using FORWARDURL:campaignid:linktext
             list($forwardcampaign, $forwardtext) = explode(':', $newforward);
         } else {
             $forwardcampaign = sprintf('%d', $newforward);
             $forwardtext = 'this link';
         }
         if (!empty($forwardcampaign)) {
             $sep = strpos($forward_url, '?') === false ? '?' : '&';
             $forwardurl = sprintf('%s%suid=%s&mid=%d', $forward_url, $sep, $subscriber->uniqid, $forwardcampaign);
             $textcampaign = str_replace($matchtext, $forwardtext . ' ' . $forwardurl, $textcampaign);
         } else {
             ## make sure to remove the match, otherwise, it'll be an eternal loop
             $textcampaign = str_replace($matchtext, '', $textcampaign);
         }
     }
     #  $result = Sql_Query(sprintf('select filename,data from %s where template = %d',
     #    Config::getTableName('templateimage'),$cached->templateid));
     if (Config::ALWAYS_ADD_USERTRACK) {
         if (stripos($htmlcampaign, '</body>')) {
             $htmlcampaign = str_replace('</body>', '<img src="' . Config::get('public_scheme') . '://' . Config::get('website') . Config::PAGEROOT . '/ut.php?u=' . $subscriber->uniqid . '&amp;m=' . $campaign->id . '" width="1" height="1" border="0" /></body>', $htmlcampaign);
         } else {
             $htmlcampaign .= '<img src="' . Config::get('public_scheme') . '://' . Config::get('website') . Config::PAGEROOT . '/ut.php?u=' . $subscriber->uniqid . '&amp;m=' . $campaign->id . '" width="1" height="1" border="0" />';
         }
     } else {
         ## can't use str_replace or str_ireplace, because those replace all, and we only want to replace one
         $htmlcampaign = preg_replace('/\\[USERTRACK\\]/i', '<img src="' . Config::get('public_scheme') . '://' . Config::get('website') . Config::PAGEROOT . '/ut.php?u=' . $subscriber->uniqid . '&amp;m=' . $campaign->id . '" width="1" height="1" border="0" />', $htmlcampaign, 1);
     }
     # make sure to only include subscribertrack once, otherwise the stats would go silly
     $htmlcampaign = str_ireplace('[USERTRACK]', '', $htmlcampaign);
     $html['subject'] = $text['subject'] = $cached_campaign->subject;
     $htmlcampaign = PrepareCampaign::parsePlaceHolders($htmlcampaign, $html);
     $textcampaign = PrepareCampaign::parsePlaceHolders($textcampaign, $text);
     if ($get_speed_stats) {
         phpList::log()->debug('parse placeholders end', ['page' => 'preparecampaign']);
     }
     if ($get_speed_stats) {
         phpList::log()->debug('parse subscriberdata start', ['page' => 'preparecampaign']);
     }
     $subscriberdata = array();
     foreach (Subscriber::$DB_ATTRIBUTES as $key) {
         $subscriberdata[$key] = $subscriber->{$key};
     }
     $htmlcampaign = PrepareCampaign::parsePlaceHolders($htmlcampaign, $subscriberdata);
     $textcampaign = PrepareCampaign::parsePlaceHolders($textcampaign, $subscriberdata);
     //CUT 2
     $destinationemail = '';
     if (is_array($subscriber_att_values)) {
         // CUT 3
         $htmlcampaign = PrepareCampaign::parsePlaceHolders($htmlcampaign, $subscriber_att_values);
         $textcampaign = PrepareCampaign::parsePlaceHolders($textcampaign, $subscriber_att_values);
     }
     if ($get_speed_stats) {
         phpList::log()->debug('parse subscriberdata end', ['page' => 'preparecampaign']);
     }
     if (!$destinationemail) {
         $destinationemail = $subscriber->getEmailAddress();
     }
     # this should move into a plugin
     if (strpos($destinationemail, '@') === false && Config::get('expand_unqualifiedemail', false) !== false) {
         $destinationemail .= Config::get('expand_unqualifiedemail');
     }
     if ($get_speed_stats) {
         phpList::log()->debug('pass to plugins for destination email start', ['page' => 'preparecampaign']);
     }
     /*TODO: enable plugins
             foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
             #    print "Checking Destination for ".$plugin->name."<br/>";
                 $destinationemail = $plugin->setFinalDestinationEmail($campaign->id, $subscriber_att_values, $destinationemail);
             }
             if ($getspeedstats) {
                 phpList::log()->debug('pass to plugins for destination email end', ['page' => 'preparecampaign']);;
             }
     
             foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
                 $textcampaign = $plugin->parseOutgoingTextCampaign($campaign->id, $textcampaign, $destinationemail, $subscriberdata);
                 $htmlcampaign = $plugin->parseOutgoingHTMLCampaign($campaign->id, $htmlcampaign, $destinationemail, $subscriberdata);
             }*/
     ## click tracking
     # for now we won't click track forwards, as they are not necessarily subscribers, so everything would fail
     if ($get_speed_stats) {
         phpList::log()->debug('click track start', ['page' => 'preparecampaign']);
     }
     if (Config::CLICKTRACK && $subscriber->uniqid != 'forwarded') {
         $urlbase = '';
         # let's leave this for now
         /*
         if (preg_match('/<base href="(.*)"([^>]*)>/Umis',$htmlcampaign,$regs)) {
           $urlbase = $regs[1];
         } else {
           $urlbase = '';
         }
                 #    print "URLBASE: $urlbase<br/>";
         */
         # convert html campaign
         #preg_match_all('/<a href="?([^> "]*)"?([^>]*)>(.*)<\/a>/Umis',$htmlcampaign,$links);
         preg_match_all('/<a (.*)href=["\'](.*)["\']([^>]*)>(.*)<\\/a>/Umis', $htmlcampaign, $links);
         # to process the Yahoo webpage with base href and link like <a href=link> we'd need this one
         #preg_match_all('/<a href=([^> ]*)([^>]*)>(.*)<\/a>/Umis',$htmlcampaign,$links);
         $clicktrack_root = sprintf('%s://%s/lt.php', Config::get('public_scheme'), Config::get('website') . Config::PAGEROOT);
         for ($i = 0; $i < count($links[2]); $i++) {
             $link = Util::cleanUrl($links[2][$i]);
             $link = str_replace('"', '', $link);
             if (preg_match('/\\.$/', $link)) {
                 $link = substr($link, 0, -1);
             }
             //$linkid = 0;
             $linktext = $links[4][$i];
             ## if the link is text containing a "protocol" eg http:// then do not track it, otherwise
             ## it will look like Phishing
             ## it's ok when the link is an image
             $linktext = strip_tags($linktext);
             $looks_like_phishing = stripos($linktext, 'https://') !== false || stripos($linktext, 'http://') !== false;
             if (!$looks_like_phishing && (preg_match('/^http|ftp/', $link) || preg_match('/^http|ftp/', $urlbase)) && stripos($link, 'www.phplist.com') === false && !strpos($link, $clicktrack_root)) {
                 # take off personal uids
                 $url = Util::cleanUrl($link, array('PHPSESSID', 'uid'));
                 #$url = preg_replace('/&uid=[^\s&]+/','',$link);
                 #if (!strpos('http:',$link)) {
                 #   $link = $urlbase . $link;
                 #}
                 $linkid = PrepareCampaign::clickTrackLinkId($campaign->id, $subscriber->id, $url, $link);
                 $masked = "H|{$linkid}|{$campaign->id}|" . $subscriber->id ^ Config::get('XORmask');
                 $masked = base64_encode($masked);
                 ## 15254- the encoding adds one or two extraneous = signs, take them off
                 $masked = preg_replace('/=$/', '', $masked);
                 $masked = preg_replace('/=$/', '', $masked);
                 $masked = urlencode($masked);
                 if (Config::get('CLICKTRACK_LINKMAP', false) === false) {
                     $newlink = sprintf('<a %shref="%s://%s/lt.php?id=%s" %s>%s</a>', $links[1][$i], Config::get('public_scheme'), Config::get('website') . Config::PAGEROOT, $masked, $links[3][$i], $links[4][$i]);
                 } else {
                     $newlink = sprintf('<a %shref="%s://%s%s" %s>%s</a>', $links[1][$i], Config::get('public_scheme'), Config::get('website') . Config::get('CLICKTRACK_LINKMAP'), $masked, $links[3][$i], $links[4][$i]);
                 }
                 $htmlcampaign = str_replace($links[0][$i], $newlink, $htmlcampaign);
             }
         }
         # convert Text campaign
         # first find occurances of our top domain, to avoid replacing them later
         # hmm, this is no point, it's not just *our* topdomain, but any
         if (0) {
             preg_match_all('#(https?://' . Config::get('website') . '/?)\\s+#mis', $textcampaign, $links);
             #preg_match_all('#(https?://[a-z0-9\./\#\?&:@=%\-]+)#ims',$textcampaign,$links);
             #preg_match_all('!(https?:\/\/www\.[a-zA-Z0-9\.\/#~\?+=&%@-_]+)!mis',$textcampaign,$links);
             for ($i = 0; $i < count($links[1]); $i++) {
                 # not entirely sure why strtolower was used, but it seems to break things http://mantis.phplist.com/view.php?id=4406
                 #$link = strtolower(cleanUrl($links[1][$i]));
                 $link = Util::cleanUrl($links[1][$i]);
                 if (preg_match('/\\.$/', $link)) {
                     $link = substr($link, 0, -1);
                 }
                 $linkid = 0;
                 if (preg_match('/^http|ftp/', $link) && stripos($link, 'www.phplist.com') === false && !strpos($link, $clicktrack_root)) {
                     $url = Util::cleanUrl($link, array('PHPSESSID', 'uid'));
                     phpList::DB()->query(sprintf('INSERT IGNORE INTO %s (campaignid, userid, url, forward)
                              VALUES(%d, %d, "%s", "%s")', Config::getTableName('linktrack'), $campaign->id, $subscriber->id, $url, $link));
                     $result = phpList::DB()->query(sprintf('SELECT linkid FROM %s
                             WHERE campaignid = %s
                             AND userid = %d
                             AND forward = "%s"', Config::getTableName('linktrack'), $campaign->id, $subscriber->id, $link));
                     $linkid = $result->fetchColumn(0);
                     $masked = "T|{$linkid}|{$campaign->id}|" . $subscriber->id ^ Config::get('XORmask');
                     $masked = urlencode(base64_encode($masked));
                     $newlink = sprintf('%s://%s/lt.php?id=%s', Config::get('public_scheme'), Config::get('website') . Config::PAGEROOT, $masked);
                     $textcampaign = str_replace($links[0][$i], '<' . $newlink . '>', $textcampaign);
                 }
             }
         }
         #now find the rest
         # @@@ needs to expand to find complete urls like:
         #http://user:password@www.web-site.com:1234/document.php?parameter=something&otherpar=somethingelse#anchor
         # or secure
         #https://user:password@www.website.com:2345/document.php?parameter=something%20&otherpar=somethingelse#anchor
         preg_match_all('#(https?://[^\\s\\>\\}\\,]+)#mis', $textcampaign, $links);
         #preg_match_all('#(https?://[a-z0-9\./\#\?&:@=%\-]+)#ims',$textcampaign,$links);
         #preg_match_all('!(https?:\/\/www\.[a-zA-Z0-9\.\/#~\?+=&%@-_]+)!mis',$textcampaign,$links);
         ## sort the results in reverse order, so that they are replaced correctly
         rsort($links[1]);
         $newlinks = array();
         for ($i = 0; $i < count($links[1]); $i++) {
             $link = Util::cleanUrl($links[1][$i]);
             if (preg_match('/\\.$/', $link)) {
                 $link = substr($link, 0, -1);
             }
             $linkid = 0;
             if (preg_match('/^http|ftp/', $link) && stripos($link, 'www.phplist.com') === false) {
                 # && !strpos($link,$clicktrack_root)) {
                 $url = Util::cleanUrl($link, array('PHPSESSID', 'uid'));
                 $linkid = PrepareCampaign::clickTrackLinkId($campaign->id, $subscriber->id, $url, $link);
                 $masked = "T|{$linkid}|{$campaign->id}|" . $subscriber->id ^ Config::get('XORmask');
                 $masked = base64_encode($masked);
                 ## 15254- the encoding adds one or two extraneous = signs, take them off
                 $masked = preg_replace('/=$/', '', $masked);
                 $masked = preg_replace('/=$/', '', $masked);
                 $masked = urlencode($masked);
                 if (Config::get('CLICKTRACK_LINKMAP', false) === false) {
                     $newlinks[$linkid] = sprintf('%s://%s/lt.php?id=%s', Config::get('public_scheme'), Config::get('website') . Config::PAGEROOT, $masked);
                 } else {
                     $newlinks[$linkid] = sprintf('%s://%s%s', Config::get('public_scheme'), Config::get('website') . Config::get('CLICKTRACK_LINKMAP'), $masked);
                 }
                 #print $links[0][$i] .' -> '.$newlink.'<br/>';
                 $textcampaign = str_replace($links[1][$i], '[%%%' . $linkid . '%%%]', $textcampaign);
             }
         }
         foreach ($newlinks as $linkid => $newlink) {
             $textcampaign = str_replace('[%%%' . $linkid . '%%%]', $newlink, $textcampaign);
         }
     }
     if ($get_speed_stats) {
         phpList::log()->debug('click track end', ['page' => 'preparecampaign']);
     }
     ## if we're not tracking clicks, we should add Google tracking here
     ## otherwise, we can add it when redirecting on the click
     if (!Config::CLICKTRACK && !empty($cached_campaign->google_track)) {
         preg_match_all('/<a (.*)href=["\'](.*)["\']([^>]*)>(.*)<\\/a>/Umis', $htmlcampaign, $links);
         for ($i = 0; $i < count($links[2]); $i++) {
             $link = Util::cleanUrl($links[2][$i]);
             $link = str_replace('"', '', $link);
             ## http://www.google.com/support/analytics/bin/answer.py?hl=en&answer=55578
             $trackingcode = 'utm_source=emailcampaign' . $campaign->id . '&utm_medium=phpList&utm_content=HTMLemail&utm_campaign=' . urlencode($cached_campaign->subject);
             ## take off existing tracking code, if found
             if (strpos($link, 'utm_medium') !== false) {
                 $link = preg_replace('/utm_(\\w+)\\=[^&]+&/U', '', $link);
             }
             if (strpos($link, '?')) {
                 $newurl = $link . '&' . $trackingcode;
             } else {
                 $newurl = $link . '?' . $trackingcode;
             }
             #   print $link. ' '.$newurl.' <br/>';
             $newlink = sprintf('<a %shref="%s" %s>%s</a>', $links[1][$i], $newurl, $links[3][$i], $links[4][$i]);
             $htmlcampaign = str_replace($links[0][$i], $newlink, $htmlcampaign);
         }
         preg_match_all('#(https?://[^\\s\\>\\}\\,]+)#mis', $textcampaign, $links);
         rsort($links[1]);
         $newlinks = array();
         for ($i = 0; $i < count($links[1]); $i++) {
             $link = Util::cleanUrl($links[1][$i]);
             if (preg_match('/\\.$/', $link)) {
                 $link = substr($link, 0, -1);
             }
             if (preg_match('/^http|ftp/', $link) && stripos($link, 'www.phplist.com') !== 0) {
                 # && !strpos($link,$clicktrack_root)) {
                 //$url = Util::cleanUrl($link, array('PHPSESSID', 'uid'));
                 $trackingcode = 'utm_source=emailcampaign' . $campaign->id . '&utm_medium=phpList&utm_content=textemail&utm_campaign=' . urlencode($cached_campaign->subject);
                 ## take off existing tracking code, if found
                 if (strpos($link, 'utm_medium') !== false) {
                     $link = preg_replace('/utm_(\\w+)\\=[^&]+/', '', $link);
                 }
                 if (strpos($link, '?')) {
                     $newurl = $link . '&' . $trackingcode;
                 } else {
                     $newurl = $link . '?' . $trackingcode;
                 }
                 $newlinks[$i] = $newurl;
                 $textcampaign = str_replace($links[1][$i], '[%%%' . $i . '%%%]', $textcampaign);
             }
         }
         foreach ($newlinks as $linkid => $newlink) {
             $textcampaign = str_replace('[%%%' . $linkid . '%%%]', $newlink, $textcampaign);
         }
         unset($newlinks);
     }
     #print htmlspecialchars($htmlcampaign);exit;
     #0011996: forward to friend - personal campaign
     if (Config::FORWARD_PERSONAL_NOTE_SIZE && $subscriber->uniqid == 'forwarded' && !empty($forwardedby['personalNote'])) {
         $htmlcampaign = nl2br($forwardedby['personalNote']) . '<br/>' . $htmlcampaign;
         $textcampaign = $forwardedby['personalNote'] . "\n" . $textcampaign;
     }
     if ($get_speed_stats) {
         phpList::log()->debug('cleanup start', ['page' => 'preparecampaign']);
     }
     ## allow fallback to default value for the ones that do not have a value
     ## delimiter is %% to avoid interfering with markup
     preg_match_all('/\\[.*\\%\\%([^\\]]+)\\]/Ui', $htmlcampaign, $matches);
     for ($i = 0; $i < count($matches[0]); $i++) {
         $htmlcampaign = str_ireplace($matches[0][$i], $matches[1][$i], $htmlcampaign);
     }
     preg_match_all('/\\[.*\\%\\%([^\\]]+)\\]/Ui', $textcampaign, $matches);
     for ($i = 0; $i < count($matches[0]); $i++) {
         $textcampaign = str_ireplace($matches[0][$i], $matches[1][$i], $textcampaign);
     }
     ## remove any remaining placeholders
     ## 16671 - do not do this, as it'll remove conditional CSS and other stuff
     ## that we'd like to keep
     //$htmlcampaign = preg_replace("/\[[A-Z\. ]+\]/i","",$htmlcampaign);
     //$textcampaign = preg_replace("/\[[A-Z\. ]+\]/i","",$textcampaign);
     #print htmlspecialchars($htmlcampaign);exit;
     # check that the HTML campaign as proper <head> </head> and <body> </body> tags
     # some readers fail when it doesn't
     if (!preg_match("#<body.*</body>#ims", $htmlcampaign)) {
         $htmlcampaign = '<body>' . $htmlcampaign . '</body>';
     }
     if (!preg_match("#<head.*</head>#ims", $htmlcampaign)) {
         if (!$adddefaultstyle) {
             $defaultstyle = "";
         }
         $htmlcampaign = '<head>
     <meta content="text/html;charset=' . $cached_campaign->html_charset . '" http-equiv="Content-Type">
     <title></title>' . $defaultstyle . '</head>' . $htmlcampaign;
     }
     if (!preg_match("#<html.*</html>#ims", $htmlcampaign)) {
         $htmlcampaign = '<html>' . $htmlcampaign . '</html>';
     }
     ## remove trailing code after </html>
     $htmlcampaign = preg_replace('#</html>.*#msi', '</html>', $htmlcampaign);
     ## the editor sometimes places <p> and </p> around the URL
     $htmlcampaign = str_ireplace('<p><!DOCTYPE', '<!DOCTYPE', $htmlcampaign);
     $htmlcampaign = str_ireplace('</html></p>', '</html>', $htmlcampaign);
     if ($get_speed_stats) {
         phpList::log()->debug('cleanup end', ['page' => 'preparecampaign']);
     }
     #  $htmlcampaign = compressContent($htmlcampaign);
     # print htmlspecialchars($htmlcampaign);exit;
     if ($get_speed_stats) {
         phpList::log()->debug('build Start ' . Config::get('processqueue_timer')->interval(1), ['page' => 'preparecampaign']);
     }
     # build the email
     $mail = new phpListMailer($campaign->id, $destinationemail);
     if ($forwardedby) {
         $mail->add_timestamp();
     }
     $mail->addCustomHeader("List-Help: <" . $text['preferences'] . ">");
     $mail->addCustomHeader("List-Unsubscribe: <" . $text['jumpoffurl'] . ">");
     $mail->addCustomHeader("List-Subscribe: <" . Config::get("subscribeurl") . ">");
     $mail->addCustomHeader("List-Owner: <mailto:" . Config::get("admin_address") . ">");
     list($dummy, $domaincheck) = explode('@', $destinationemail);
     $text_domains = explode("\n", trim(Config::get("alwayssendtextto")));
     if (in_array($domaincheck, $text_domains)) {
         $subscriber->htmlemail = 0;
         if (Config::VERBOSE) {
             phpList::log()->debug(s('sendingtextonlyto') . " {$domaincheck}", ['page' => 'preparecampaign']);
         }
     }
     /*TODO: enable plugins
       foreach (Config::get('plugins') as $pluginname => $plugin) {
           #$textcampaign = $plugin->parseOutgoingTextCampaign($campaign->id,$textcampaign,$destinationemail, $subscriberdata);
           #$htmlcampaign = $plugin->parseOutgoingHTMLCampaign($campaign->id,$htmlcampaign,$destinationemail, $subscriberdata);
           $plugin_attachments = $plugin->getCampaignAttachment($campaign->id, $mail->Body);
           if (!empty($plugin_attachments[0]['content'])) {
               foreach ($plugins_attachments as $plugin_attachment) {
                   $mail->add_attachment(
                       $plugin_attachment['content'],
                       basename($plugin_attachment['filename']),
                       $plugin_attachment['mimetype']
                   );
               }
           }
       }*/
     # so what do we actually send?
     switch ($cached_campaign->sendformat) {
         case "PDF":
             # send a PDF file to subscribers who want html and text to everyone else
             /*TODO: enable plugins
               foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
                   $plugin->processSuccesFailure($campaign->id, 'astext', $subscriberdata);
               }*/
             if ($subscriber->htmlemail) {
                 if (!$is_test_mail) {
                     $campaign->aspdf += 1;
                     $campaign->update();
                 }
                 $pdffile = PrepareCampaign::createPdf($textcampaign);
                 if (is_file($pdffile) && filesize($pdffile)) {
                     $fp = fopen($pdffile, "r");
                     if ($fp) {
                         $contents = fread($fp, filesize($pdffile));
                         fclose($fp);
                         unlink($pdffile);
                         $html = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
           <html>
           <head>
             <title></title>
           </head>
           <body>
           <embed src="campaign.pdf" width="450" height="450" href="campaign.pdf"></embed>
           </body>
           </html>';
                         #$mail->add_html($html,$textcampaign);
                         #$mail->add_text($textcampaign);
                         $mail->addAttachment($contents, "campaign.pdf", "application/pdf");
                     }
                 }
                 PrepareCampaign::addAttachments($campaign, $mail, "HTML");
             } else {
                 if (!$is_test_mail) {
                     phpList::DB()->query("UPDATE {Config::getTableName('campaign')} SET astext = astext + 1 WHERE id = {$campaign->id}");
                 }
                 $mail->add_text($textcampaign);
                 PrepareCampaign::addAttachments($campaign, $mail, "text");
             }
             break;
         case "text and PDF":
             /*TODO: enable plugins
               foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
                   $plugin->processSuccesFailure($campaign->id, 'astext', $subscriberdata);
               }*/
             # send a PDF file to subscribers who want html and text to everyone else
             if ($subscriber->htmlemail) {
                 if (!$is_test_mail) {
                     phpList::DB()->query("UPDATE {Config::getTableName('campaign')} SET astextandpdf = astextandpdf + 1 WHERE id = {$campaign->id}");
                 }
                 $pdffile = PrepareCampaign::createPDF($textcampaign);
                 if (is_file($pdffile) && filesize($pdffile)) {
                     $fp = fopen($pdffile, "r");
                     if ($fp) {
                         $contents = fread($fp, filesize($pdffile));
                         fclose($fp);
                         unlink($pdffile);
                         $html = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
           <html>
           <head>
             <title></title>
           </head>
           <body>
           <embed src="campaign.pdf" width="450" height="450" href="campaign.pdf"></embed>
           </body>
           </html>';
                         #           $mail->add_html($html,$textcampaign);
                         $mail->add_text($textcampaign);
                         $mail->add_attachment($contents, "campaign.pdf", "application/pdf");
                     }
                 }
                 PrepareCampaign::addAttachments($campaign, $mail, "HTML");
             } else {
                 if (!$is_test_mail) {
                     phpList::DB()->query("UPDATE {Config::getTableName('message')} SET astext = astext + 1 WHERE id = {$campaign->id}");
                 }
                 $mail->add_text($textcampaign);
                 PrepareCampaign::addAttachments($campaign, $mail, "text");
             }
             break;
         case "text":
             # send as text
             /*TODO: enable plugins
               foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
                   $plugin->processSuccesFailure($campaign->id, 'astext', $subscriberdata);
               }*/
             if (!$is_test_mail) {
                 phpList::DB()->query("UPDATE {Config::getTableName('message')} SET astext = astext + 1 WHERE id = {$campaign->id}");
             }
             $mail->add_text($textcampaign);
             PrepareCampaign::addAttachments($campaign, $mail, "text");
             break;
         case "both":
         case "text and HTML":
         case "HTML":
         default:
             $handled_by_plugin = 0;
             /*TODO: enable plugins
               if (!empty($GLOBALS['pluginsendformats'][$cached_campaign->sendformat])) {
                   # possibly handled by plugin
                   $pl = $GLOBALS['plugins'][$GLOBALS['pluginsendformats'][$cached_campaign->sendformat]];
                   if (is_object($pl) && method_exists($pl, 'parseFinalCampaign')) {
                       $handled_by_plugin = $pl->parseFinalCampaign(
                           $cached_campaign->sendformat,
                           $htmlcampaign,
                           $textcampaign,
                           $mail,
                           $campaign->id
                       );
                   }
               }
               */
             if (!$handled_by_plugin) {
                 # send one big file to subscribers who want html and text to everyone else
                 if ($subscriber->htmlemail) {
                     if (!$is_test_mail) {
                         phpList::DB()->query("update {Config::getTableName('message')} set astextandhtml = astextandhtml + 1 where id = {$campaign->id}");
                     }
                     /*TODO: enable plugins
                       foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
                           $plugin->processSuccesFailure($campaign->id, 'ashtml', $subscriberdata);
                       }*/
                     #  dbg("Adding HTML ".$cached->templateid);
                     if (Config::WORDWRAP_HTML) {
                         ## wrap it: http://mantis.phplist.com/view.php?id=15528
                         ## some reports say, this fixes things and others say it breaks things https://mantis.phplist.com/view.php?id=15617
                         ## so for now, only switch on if requested.
                         ## it probably has to do with the MTA used
                         $htmlcampaign = wordwrap($htmlcampaign, Config::WORDWRAP_HTML, "\r\n");
                     }
                     $mail->add_html($htmlcampaign, $textcampaign, $cached_campaign->templateid);
                     PrepareCampaign::addAttachments($campaign, $mail, "HTML");
                 } else {
                     if (!$is_test_mail) {
                         phpList::DB()->query("UPDATE {Config::getTableName('message')} SET astext = astext + 1 WHERE id = {$campaign->id}");
                     }
                     /*TODO: enable plugins
                       foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
                           $plugin->processSuccesFailure($campaign->id, 'astext', $subscriberdata);
                       }*/
                     $mail->add_text($textcampaign);
                     #$mail->setText($textcampaign);
                     #$mail->Encoding = TEXTEMAIL_ENCODING;
                     PrepareCampaign::addAttachments($campaign, $mail, "text");
                 }
             }
             break;
     }
     #print htmlspecialchars($htmlcampaign);exit;
     if (!Config::TEST) {
         if ($subscriber->uniqid != 'forwarded' || !sizeof($forwardedby)) {
             $fromname = $cached_campaign->fromname;
             $fromemail = $cached_campaign->fromemail;
             $subject = $cached_campaign->subject;
         } else {
             $fromname = '';
             $fromemail = $forwardedby['email'];
             $subject = s('Fwd') . ': ' . $cached_campaign->subject;
         }
         if (!empty($cached_campaign->replytoemail)) {
             $mail->AddReplyTo($cached_campaign->replytoemail, $cached_campaign->replytoname);
         }
         if ($get_speed_stats) {
             phpList::log()->debug('build End ' . Timer::get('PQT')->interval(1), ['page' => 'preparecampaign']);
         }
         if ($get_speed_stats) {
             phpList::log()->debug('send Start ' . Timer::get('PQT')->interval(1), ['page' => 'preparecampaign']);
         }
         if (DEBUG) {
             $destinationemail = PHPLIST_DEVELOPER_EMAIL;
         }
         if (!$mail->compatSend('', $destinationemail, $fromname, $fromemail, $subject)) {
             #if (!$mail->send(array($destinationemail),'spool')) {
             /*TODO: enable plugins
               foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
                   $plugin->processSendFailed($campaign->id, $subscriberdata, $isTestMail);
               }*/
             phpList::log()->debug(sprintf(s('Error sending campaign %d (%d/%d) to %s (%s) '), $campaign->id, 0, 0, $subscriber->getEmailAddress(), $destinationemail), ['page' => 'preparecampaign']);
             return false;
         } else {
             ## only save the estimated size of the campaign when sending a test campaign
             if ($get_speed_stats) {
                 phpList::log()->debug('send End ' . Timer::get('PQT')->interval(1), ['page' => 'preparecampaign']);
             }
             //TODO: find solution for send process id global var which currently is definded in CampaignQueue
             if (!isset($GLOBALS['send_process_id'])) {
                 if (!empty($mail->mailsize)) {
                     $name = $subscriber->htmlemail ? 'htmlsize' : 'textsize';
                     $campaign->setDataItem($name, $mail->mailsize);
                 }
             }
             $sqlCount = phpList::DB()->getQueryCount() - $sql_count_start;
             if ($get_speed_stats) {
                 phpList::log()->debug('It took ' . $sqlCount . '  queries to send this campaign', ['page' => 'preparecampaign']);
             }
             /*TODO:enable plugins
               foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
                   $plugin->processSendSuccess($campaign->id, $subscriberdata, $isTestMail);
               }*/
             #   logEvent("Sent campaign $campaign->id to $subscriber->getEmailAddress() ($destinationemail)");
             return true;
         }
     }
     return false;
 }
Exemple #2
0
 /**
  * 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&amp;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;
 }