public static function sendAdminCopy($subject, $message, $lists = array()) { $sendcopy = Config::get('send_admin_copies'); if ($sendcopy) { //$lists = cleanArray($lists); $mails = array(); if (sizeof($lists) && Config::get('SEND_LISTADMIN_COPY')) { foreach ($lists as $list) { $tmp_list = MailingList::getListById($list); $mails[] = Admin::getAdmin($tmp_list->id)->email; } } ## hmm, do we want to be exclusive? Either listadmin or main ones ## could do all instead if (!sizeof($mails)) { $admin_mail = Config::get('admin_address'); if ($c = Config::get('admin_addresses')) { $mails = explode(',', $c); } $mails[] = $admin_mail; } $sent = array(); foreach ($mails as $admin_mail) { $admin_mail = trim($admin_mail); if (!isset($sent[$admin_mail]) && !empty($admin_mail)) { phpListMailer::sendMail($admin_mail, $subject, $message, phpListMailer::systemMessageHeaders($admin_mail)); phpList::log()->notice(s('Sending admin copy to') . ' ' . $admin_mail); $sent[$admin_mail] = 1; } } } }
/** * Duplicate a campaign and reschedule * @param CampaignEntity $campaign */ public function repeatCampaign(CampaignEntity $campaign) { # if (!USE_REPETITION && !USE_rss) return; ## do not repeat when it has already been done if ($campaign->repeatuntil->getTimestamp() < time() && (!empty($campaign->repeatedid) || $campaign->repeatinterval == 0)) { return; } # get the future embargo, either "repeat" minutes after the old embargo # or "repeat" after this very moment to make sure that we're not sending the # campaign every time running the queue when there's no embargo set. $new_embargo = $campaign->embargo->add(\DateInterval::createFromDateString($campaign->repeatinterval . ' minutes')); $now = new \DateTime(); $new_embargo2 = $now->add(\DateInterval::createFromDateString($campaign->repeatinterval . ' minutes')); $is_fututre = $new_embargo->getTimestamp() > time(); # copy the new campaign $new_campaign = $campaign; $new_campaign->id = 0; $this->update($new_campaign); //also need to copy the campaign data for this one $this->db->query(sprintf('INSERT INTO %s(name, id, data) SELECT name, %d, data FROM %s WHERE id = %d', $this->config->getTableName('messagedata'), $new_campaign->id, $this->config->getTableName('messagedata'), $campaign->id)); # check whether the new embargo is not on an exclusion if ($this->config->get('repeat_exclude', false) !== false) { $loopcnt = 0; $repeatinterval = 0; while ($this->excludedDateForRepetition($new_embargo)) { $repeatinterval += $new_campaign->repeatinterval; $loopcnt++; $new_embargo = $new_campaign->embargo->add(\DateInterval::createFromDateString($repeatinterval . ' minutes')); $now = new \DateTime(); $new_embargo2 = $now->add(\DateInterval::createFromDateString($repeatinterval . ' minutes')); $is_fututre = $new_embargo->getTimestamp() > time(); if ($loopcnt > 15) { //TODO: remove static call phpList::log()->notice('Unable to find new embargo date too many exclusions? for campaign ' . $new_campaign->id); return; } } } # correct some values if (!$is_fututre) { $new_embargo = $new_embargo2; } $new_campaign->embargo = $new_embargo; $new_campaign->status = 'submitted'; $new_campaign->sent = ''; foreach (array("processed", "astext", "ashtml", "astextandhtml", "aspdf", "astextandpdf", "viewed", "bouncecount") as $item) { $new_campaign->{$item} = 0; } $this->update($new_campaign); # lists $this->db->query(sprintf('INSERT INTO %s(messageid,listid,entered) SELECT %d, listid, CURRENT_TIMESTAMP FROM %s WHERE messageid = %d', $this->config->getTableName('listmessage'), $new_campaign->id, $this->config->getTableName('listmessage'), $campaign->id)); # attachments $attachments = $this->getAttachments($campaign); foreach ($attachments as $attachment) { $attachment->id = 0; if (is_file($attachment->remotefile)) { $attachment->file = ''; } $this->addAttachment($new_campaign, $attachment); } //TODO: remove static call phpList::log()->notice("Campaign {$campaign->id} was successfully rescheduled as campaign {$new_campaign->id}"); ## remember we duplicated, in order to avoid doing it again (eg when requeuing) $this->setDataItem($campaign, 'repeatedid', $new_campaign->id); }
/** * PHP >= 5.4.0<br/> * Write session data * @link http://php.net/manual/en/sessionhandlerinterafce.write.php * @param string $session_id The session id. * @param string $session_data <p> * The encoded session data. This data is the * result of the PHP internally encoding * the $_SESSION superglobal to a serialized * string and passing it as this parameter. * Please note sessions use an alternative serialization method. * </p> * @return bool <p> * The return value (usually TRUE on success, FALSE on failure). * Note this value is returned internally to PHP for processing. * </p> */ public function write($session_id, $session_data) { $session_id = addslashes($session_id); $session_data = addslashes($session_data); $session_exists = phpList::DB()->query(sprintf('SELECT COUNT(*) FROM %s WHERE sessionid = \'%s\'', Config::SESSION_TABLENAME, addslashes($session_id)))->fetchColumn(0); if ($session_exists <= 0) { $retval = phpList::DB()->query(sprintf('INSERT INTO %s (sessionid,lastactive,data) VALUES("%s",UNIX_TIMESTAMP(NOW()),"%s")', Config::SESSION_TABLENAME, $session_id, $session_data)); } else { $retval = phpList::DB()->query(sprintf('UPDATE %s SET data = "%s", lastactive = UNIX_TIMESTAMP(NOW()) WHERE sessionid = "%s"', Config::SESSION_TABLENAME, $session_id, $session_data)); if ($retval->rowCount() <= 0) { //TODO: correct error handling phpList::log()->notice('unable to update session data for session ' . $session_id); sendError('unable to update session data for session ' . $session_id); } } return $retval; }
/** * @param resource $link IMAP stream * @param int $max * @return bool */ private function processMessages($link, $max = 3000) { $num = imap_num_msg($link); $this->output($num . ' ' . s('bounces to fetch from the mailbox') . "\n"); $this->output(s('Please do not interrupt this process') . "\n"); phpList::log()->addToReport($num . ' ' . s('bounces to process') . "\n"); if ($num > $max) { phpList::log()->addToReport($num . ' ' . s('processing first') . " {$max} " . s('bounces') . "\n"); $num = $max; } if (Config::TEST) { phpList::log()->info(s('Running in test mode, not deleting messages from mailbox'), ['page' => 'processbounces']); } else { phpList::log()->info(s('Processed messages will be deleted from mailbox'), ['page' => 'processbounces']); } # for ($x=1;$x<150;$x++) { for ($x = 1; $x <= $num; $x++) { set_time_limit(60); $header = imap_fetchheader($link, $x); if ($x % 25 == 0) { # $this->output( $x . " ". nl2br($header)); $this->output($x . ' done', 1); } $processed = $this->processImapBounce($link, $x, $header); if ($processed) { if (!Config::TEST && Config::BOUNCE_MAILBOX_PURGE) { if (Config::VERBOSE) { $this->output(s('Deleting message') . ' ' . $x); } imap_delete($link, $x); } elseif (Config::VERBOSE) { $this->output(s('Not deleting processed message') . ' ' . $x . ' ' . Config::BOUNCE_MAILBOX_PURGE); } } else { if (!Config::TEST && Config::BOUNCE_MAILBOX_PURGE_UNPROCESSED) { if (Config::VERBOSE) { $this->output(s('Deleting message') . ' ' . $x); } imap_delete($link, $x); } elseif (Config::VERBOSE) { $this->output(s('Not deleting unprocessed message') . ' ' . $x); } } } $this->output(s('Closing mailbox, and purging messages')); set_time_limit(60 * $num); imap_close($link); /*if ($num) return $report;*/ return $num > 0; }
/** * Check if a form token is valid * @param string $token * @return bool */ public function verifyToken($token) { if (empty($token)) { return false; } ## @@@TODO for now ignore the error. This will cause a block on editing admins if the table doesn't exist. $result = phpList::DB()->prepare(sprintf('SELECT id FROM %s WHERE adminid = %d AND value = :token AND expires > CURRENT_TIMESTAMP', Config::getTableName('admintoken'), $this->id)); $result->execute(array(':token' => $token)); return $result->rowCount() > 0; }
public static function releaseLock($processid) { if (!$processid) { return; } phpList::DB()->query('DELETE FROM %s WHERE id = %d', Config::getTableName('sendprocess'), $processid); }
public static function flushClickTrackCache() { if (count(Cache::$_instance->linktrack_sent_cache) == 0) { return; } foreach (Cache::$_instance->linktrack_sent_cache as $mid => $numsent) { foreach ($numsent as $fwdid => $fwdtotal) { if (Config::VERBOSE) { phpList::log()->debug("Flushing clicktrack stats for {$mid}: {$fwdid} => {$fwdtotal}", ['page' => 'cache']); } phpList::DB()->query(sprintf('UPDATE %s SET total = %d WHERE messageid = %d AND forwardid = %d', Config::getTableName('linktrack_ml'), $fwdtotal, $mid, $fwdid)); } } }
/** * Load campaign in memory cache * @param Campaign $campaign * @param bool $forwardContent * @return bool */ public static function precacheCampaign($campaign, $forwardContent = false) { $domain = Config::get('domain'); /** * @var Campaign $cached_campaign */ $cached_campaign =& Cache::getCachedCampaign($campaign); ## the reply to is actually not in use if (preg_match('/([^ ]+@[^ ]+)/', $campaign->replyto, $regs)) { # if there is an email in the from, rewrite it as "name <email>" $campaign->replyto = str_replace($regs[0], '', $campaign->replyto); $cached_campaign->replytoemail = $regs[0]; # if the email has < and > take them out here $cached_campaign->replytoemail = str_replace(array('<', '>'), '', $cached_campaign->replytoemail); //$cached->replytoemail = str_replace('>', '', $cached->replytoemail); # make sure there are no quotes around the name $cached_campaign->replytoname = str_replace('"', '', ltrim(rtrim($campaign->replyto))); } elseif (strpos($campaign->replyto, ' ')) { # if there is a space, we need to add the email $cached_campaign->replytoname = $campaign->replyto; $cached_campaign->replytoemail = "listmaster@{$domain}"; } else { if (!empty($campaign->replyto)) { $cached_campaign->replytoemail = "{$campaign->replyto}@{$domain}"; ## makes more sense not to add the domain to the word, but the help says it does ## so let's keep it for now $cached_campaign->replytoname = "{$campaign->replyto}@{$domain}"; } } //$cached_campaign->fromname = $campaign->fromname; //$cached_campaign->fromemail = $campaign->fromemail; $cached_campaign->to = $campaign->tofield; #0013076: different content when forwarding 'to a friend' $cached_campaign->subject = $forwardContent ? stripslashes($campaign->forwardsubject) : $campaign->subject; #0013076: different content when forwarding 'to a friend' $cached_campaign->content = $forwardContent ? stripslashes($campaign->forwardcampaign) : $campaign->campaign; if (Config::USE_MANUAL_TEXT_PART && !$forwardContent) { $cached_campaign->textcontent = $campaign->textcampaign; } else { $cached_campaign->textcontent = ''; } #var_dump($cached);exit; #0013076: different content when forwarding 'to a friend' $cached_campaign->footer = $forwardContent ? stripslashes($campaign->forwardfooter) : $campaign->footer; if (strip_tags($cached_campaign->footer) != $cached_campaign->footer) { $cached_campaign->textfooter = String::HTML2Text($cached_campaign->footer); $cached_campaign->htmlfooter = $cached_campaign->footer; } else { $cached_campaign->textfooter = $cached_campaign->footer; $cached_campaign->htmlfooter = PrepareCampaign::parseText($cached_campaign->footer); } $cached_campaign->htmlformatted = strip_tags($cached_campaign->content) != $cached_campaign->content; //$cached_campaign->sendformat = $campaign->sendformat; ## @@ put this here, so it can become editable per email sent out at a later stage $cached_campaign->html_charset = 'UTF-8'; #Config::get('html_charset'); ## @@ need to check on validity of charset /*if (!$cached_campaign->html_charset) { $cached_campaign->html_charset = 'UTF-8'; #'iso-8859-1'; }*/ $cached_campaign->text_charset = 'UTF-8'; #Config::get('text_charset'); /*if (!$cached_campaign->text_charset) { $cached_campaign->text_charset = 'UTF-8'; #'iso-8859-1'; }*/ ## if we are sending a URL that contains subscriber attributes, we cannot pre-parse the campaign here ## but that has quite some impact on speed. So check if that's the case and apply $cached_campaign->subscriberspecific_url = preg_match('/\\[.+\\]/', $campaign->sendurl); if (!$cached_campaign->subscriberspecific_url) { ## Fetch external content here, because URL does not contain placeholders if (Config::get('canFetchUrl') && preg_match('/\\[URL:([^\\s]+)\\]/i', $cached_campaign->content, $regs)) { $remote_content = Util::fetchUrl($regs[1]); # $remote_content = fetchUrl($campaign['sendurl'],array()); # @@ don't use this # $remote_content = includeStyles($remote_content); if ($remote_content) { $cached_campaign->content = str_replace($regs[0], $remote_content, $cached_campaign->content); # $cached[$campaign_id]['content'] = $remote_content; $cached_campaign->htmlformatted = strip_tags($remote_content) != $remote_content; } else { #print Error(s('unable to fetch web page for sending')); phpList::log()->notice("Error fetching URL: " . $campaign->sendurl . ' cannot proceed'); return false; } } if (Config::VERBOSE && Config::get('getspeedstats', false) !== false) { phpList::log()->debug('fetch URL end', ['page' => 'preparecampaign']); } /* print $campaign->sendurl; print $remote_content;exit; */ } // end if not subscriberspecific url /*if ($cached_campaign->htmlformatted) { # $cached->content = String::compressContent($cached->content); }*/ //$cached_campaign->google_track = $campaign->google_track; /* else { print $campaign->sendurl; exit; } */ if (Config::VERBOSE && Config::get('getspeedstats', false) !== false) { phpList::log()->debug('parse config start', ['page' => 'preparecampaign']); } /* * this is not a good idea, as it'll replace eg "unsubscribeurl" with a general one instead of personalised * if (is_array($GLOBALS['default_config'])) { foreach($GLOBALS['default_config'] as $key => $val) { if (is_array($val)) { $cached[$campaign_id]['content'] = str_ireplace("[$key]",Config::get($key),$cached[$campaign_id]['content']); $cached->textcontent = str_ireplace("[$key]",Config::get($key),$cached->textcontent); $cached->textfooter = str_ireplace("[$key]",Config::get($key),$cached[$campaign_id]['textfooter']); $cached->htmlfooter = str_ireplace("[$key]",Config::get($key),$cached[$campaign_id]['htmlfooter']); } } } */ if (Config::VERBOSE && Config::get('getspeedstats', false) !== false) { phpList::log()->debug('parse config end', ['page' => 'preparecampaign']); } /*TODO: figure out what this does foreach ($campaign as $key => $val) { if (!is_array($val)) { $cached_campaign->content = str_ireplace("[$key]", $val, $cached_campaign->content); $cached_campaign->textcontent = str_ireplace("[$key]", $val, $cached_campaign->textcontent); $cached_campaign->textfooter = str_ireplace("[$key]", $val, $cached_campaign->textfooter); $cached_campaign->htmlfooter = str_ireplace("[$key]", $val, $cached_campaign->htmlfooter); } }*/ if (preg_match("/##LISTOWNER=(.*)/", $cached_campaign->content, $regs)) { $cached_campaign->listowner = $regs[1]; $cached_campaign->content = str_replace($regs[0], '', $cached_campaign->content); } else { $cached_campaign->listowner = 0; } if (!empty($cached_campaign->listowner)) { $att_result = phpList::DB()->query(sprintf('SELECT name,value FROM %s AS aa, %s AS a_a WHERE aa.id = a_a.adminattributeid AND aa.adminid = %d', Config::getTableName('adminattribute'), Config::getTableName('admin_attribute'), $cached_campaign->listowner)); while ($att = $att_result->fetch(\PDO::FETCH_ASSOC)) { $cached_campaign->content = preg_replace('#\\[LISTOWNER.' . strtoupper(preg_quote($att['name'])) . '\\]#', $att['value'], $cached_campaign->content); } } $baseurl = Config::get('website'); if (Config::UPLOADIMAGES_DIR != null) { ## escape subdirectories, otherwise this renders empty $dir = str_replace('/', '\\/', Config::UPLOADIMAGES_DIR); $cached_campaign->content = preg_replace('/<img(.*)src="\\/' . $dir . '(.*)>/iU', '<img\\1src="' . Config::get('public_scheme') . '://' . $baseurl . '/' . Config::UPLOADIMAGES_DIR . '\\2>', $cached_campaign->content); } //if (defined('FCKIMAGES_DIR') && FCKIMAGES_DIR) { //$cached[$campaign_id]['content'] = preg_replace('/<img(.*)src="\/lists\/'.FCKIMAGES_DIR.'(.*)>/iU','<img\\1src="'.$GLOBALS['public_scheme'].'://'.$baseurl.'/lists/'.FCKIMAGES_DIR.'\\2>',$cached[$campaign_id]['content']); //} return true; }
/** * Shutdown function for execution on shutdown * @link http://php.net/manual/en/function.register-shutdown-function.php */ public function shutdown() { # phpList::log()->debug( "Script status: ".connection_status(), ['page' => 'porcessqueue']); # with PHP 4.2.1 buggy. http://bugs.php.net/bug.php?id=17774 phpList::log()->debug(s('Script stage') . ': ' . $this->script_stage, ['page' => 'porcessqueue']); $some = $this->processed; #$this->sent;# || $this->invalid || $this->notsent; if (!$some) { phpList::log()->debug(s('Finished, Nothing to do'), ['page' => 'porcessqueue']); $this->nothingtodo = 1; } $totaltime = Timer::get('process_queue')->elapsed(true); if ($totaltime > 0) { $msgperhour = 3600 / $totaltime * $this->sent; } else { $msgperhour = s('Calculating'); } if ($this->sent) { phpList::log()->debug(sprintf('%d %s %01.2f %s (%d %s)', $this->sent, s('campaigns sent in'), $totaltime, s('seconds'), $msgperhour, s('msgs/hr')), ['page' => 'processqueue']); } if ($this->invalid > 0) { phpList::log()->debug(s('%d invalid email addresses', $this->invalid), ['page' => 'porcessqueue']); } if ($this->failed_sent > 0) { phpList::log()->debug(s('%d failed (will retry later)', $this->failed_sent), ['page' => 'porcessqueue']); foreach ($this->counters as $label => $value) { # phpList::log()->debug(sprintf('%d %s',$value,s($label)),1,'progress', ['page' => 'porcessqueue']); phpList::log()->info(sprintf('%d %s', $value, s($label)), ['page' => 'processqueue']); } } if ($this->unconfirmed > 0) { phpList::log()->debug(sprintf(s('%d emails unconfirmed (not sent)'), $this->unconfirmed), ['page' => 'porcessqueue']); } /* * TODO: enable plugins foreach ($GLOBALS['plugins'] as $pluginname => $plugin) { $plugin->processSendStats($this->sent,$this->invalid,$this->failed_sent,$this->unconfirmed,$this->counters); } */ Cache::flushClickTrackCache(); Process::releaseLock($this->send_process_id); //finish("info",$report,$this->script_stage); //function finish ($flag,$campaign,$this->script_stage) { $subject = s('Maillist Processing info'); if (!$this->nothingtodo) { phpList::log()->info(s('Finished this run'), ['page' => 'progress']); 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); } } */ if ($this->script_stage < 5 && !$this->nothingtodo) { phpList::log()->info(s('Warning: script never reached stage 5') . "\n" . s('This may be caused by a too slow or too busy server') . " \n"); //TODO: remove globals } elseif ($this->script_stage == 5 && (!$this->nothingtodo || isset($GLOBALS['wait']))) { # if the script timed out in stage 5, reload the page to continue with the rest $this->reload++; if (!Config::get('commandline') && $this->num_per_batch && $this->batch_period) { if ($this->sent + 10 < $this->original_num_per_batch) { phpList::log()->debug(s('Less than batch size were sent, so reloading imminently'), ['page' => 'porcessqueue']); $delaytime = 10; } else { // TODO: we should actually want batch period minus time already spent. // might be nice to calculate that at some point phpList::log()->info(sprintf(s('Waiting for %d seconds before reloading'), $this->batch_period), ['page' => 'processqueue']); $delaytime = $this->batch_period; } sleep($delaytime); /*Output::customPrintf( '<script type="text/javascript"> document.location = "./?page=pageaction&action=processqueue&ajaxed=true&reload=%d&lastsent=%d&lastskipped=%d"; </script>', $this->reload, $this->sent, $this->notsent );*/ } else { /*Output::customPrintf( '<script type="text/javascript"> document.location = "./?page=pageaction&action=processqueue&ajaxed=true&reload=%d&lastsent=%d&lastskipped=%d"; </script>', $this->reload, $this->sent, $this->notsent );*/ } } elseif ($this->script_stage == 6 || $this->nothingtodo) { /* * TODO: enable plugins foreach ($GLOBALS['plugins'] as $pluginname => $plugin) { $plugin->campaignQueueFinished(); }*/ phpList::log()->debug(s('Finished, All done'), 0, ['page' => 'porcessqueue']); } else { phpList::log()->debug(s('Script finished, but not all campaigns have been sent yet.'), ['page' => 'porcessqueue']); } if (!Config::get('commandline') && empty($_GET['ajaxed'])) { include_once "footer.inc"; } elseif (Config::get('commandline')) { @ob_end_clean(); } exit; }