[ Avaa Bypassed ]




Upload:

Command:

hmhc3928@3.145.108.87: ~ $
<?php

require_once dirname(__FILE__).'/accesscheck.php';
// library used for plugging into the webbler, instead of "connect"
// depricated and should be removed
include_once dirname(__FILE__).'/class.phplistmailer.php';

$domain = getConfig('domain');
$website = getConfig('website');

if (!$GLOBALS['message_envelope']) {
    // why not try set it to "person in charge of this system". Will help get rid of a lot of bounces to nobody@server :-)
    $admin = getConfig('admin_address');
    if (!empty($admin)) {
        $GLOBALS['message_envelope'] = $admin;
    }
}

if (defined('IN_WEBBLER') && is_object($GLOBALS['config']['plugins']['phplist'])) {
    $GLOBALS['tables'] = $GLOBALS['config']['plugins']['phplist']->tables;
}

/* this should probably move to init.php */
$GLOBALS['bounceruleactions'] = array(
    'deleteuser'                    => $GLOBALS['I18N']->get('delete subscriber'),
    'unconfirmuser'                 => $GLOBALS['I18N']->get('unconfirm subscriber'),
    'blacklistuser'                 => $GLOBALS['I18N']->get('blacklist subscriber'),
    'blacklistemail'                => $GLOBALS['I18N']->get('blacklist email address'),
    'deleteuserandbounce'           => $GLOBALS['I18N']->get('delete subscriber and bounce'),
    'unconfirmuseranddeletebounce'  => $GLOBALS['I18N']->get('unconfirm subscriber and delete bounce'),
    'blacklistuseranddeletebounce'  => $GLOBALS['I18N']->get('blacklist subscriber and delete bounce'),
    'blacklistemailanddeletebounce' => $GLOBALS['I18N']->get('blacklist email address and delete bounce'),
    'deletebounce'                  => $GLOBALS['I18N']->get('delete bounce'),
);

if (!isset($GLOBALS['developer_email'])) {
    ini_set('error_append_string', 'phpList version '.VERSION);
    ini_set('error_prepend_string', '<p class="error">Sorry a software error occurred:<br/>
    Please <a href="https://github.com/phpList/phplist3/issues">report a bug</a> when reporting the bug, please include URL and the entire content of this page.<br/>');
}

function cleanListName($name) { ## we allow certain tags in a listname
    $name = strip_tags($name,'<br><p><h1><h2><h3><b><i>');
    $allowedAttr = array('title','class','style');

    // we need to clean attributes as well.

    preg_match_all('~<(/?)([\w]+)([^>]*)/?>~',$name,$regs);

    for ($i=0; $i< sizeof($regs[0]); $i++ ) {
        $clean = '';
        #      print $i . ' '.htmlspecialchars($regs[0][$i]).'<h2>'.$regs[1][$i].'</h2><b>'.strlen($regs[3][$i]).'</b><br/> ';
        if ($regs[1][$i] == '/') {
            $clean .= '</'.$regs[2][$i].'>';
        } elseif (!strlen($regs[3][$i]) || $regs[3][$i] == '/') {
            $clean .= '<'.$regs[2][$i].$regs[3][$i].'>';
        } else {
            $attributes = $regs[3][$i];
            preg_match_all('/([\w]+)="([^\"]+)"/',$attributes,$attrregs); // only allow properly formatted, without spacing
            $newattributes = '';
            for ($j=0; $j< sizeof($attrregs[0]); $j++ ) {
                if (in_array($attrregs[1][$j],$allowedAttr)) {
                    $newattributes .= $attrregs[1][$j].'="'.$attrregs[2][$j].'" ';
                }
            }
            $clean .= '<'.$regs[2][$i].' '.trim($newattributes).'>';
        }
        $name = str_ireplace($regs[0][$i],$clean,$name);
    }
    return $name;
}

/**
 * Returns the name of a list.
 * The list names are cached because this can be called repeatedly for the same list id.
 *
 * @param int $id list id
 *
 * @return string the list name
 */
function listName($id)
{
    global $tables;

    static $listNames = [];

    if (!isset($listNames[$id])) {
        $req = Sql_Fetch_Row_Query(sprintf('select name from %s where id = %d', $tables['list'], $id));
        $listNames[$id] = $req[0] ? stripslashes(cleanListName($req[0])) : $GLOBALS['I18N']->get('Unnamed List');
    }

    return $listNames[$id];
}

function setMessageData($msgid, $name, $value)
{
    if ($name == 'PHPSESSID') {
        return;
    }
    if ($name == session_name()) {
        return;
    }

    if ($name == 'subject' || $name == 'campaigntitle') {
        //# disallow html in the subject and title
        $value = strip_tags($value);
    }
    if ($name == 'message') { ## there's no need for js actions in the body. @@TODO expand on other fields
      $value = disableJavascript($value);
    }

    if ($name == 'targetlist' && is_array($value)) {
        Sql_query(sprintf('delete from %s where messageid = %d', $GLOBALS['tables']['listmessage'], $msgid));
        if (!empty($value['all']) || !empty($value['allactive'])) {
            $res = Sql_query('select * from '.$GLOBALS['tables']['list'].' '.$GLOBALS['subselect']);
            while ($row = Sql_Fetch_Array($res)) {
                $listid = $row['id'];
                if ($row['active'] || !empty($value['all'])) {
                    $result = Sql_query('insert ignore into '.$GLOBALS['tables']['listmessage']."  (messageid,listid,entered) values($msgid,$listid,now())");
                }
            }
            //# once we used "all" to set all, unset it, to avoid confusion trying to unselect lists
            unset($value['all']);
        } else {
            foreach ($value as $listid => $val) {
                if ($listid != 'unselect') { //# see #16940 - ignore a list called "unselect" which is there to allow unselecting all
                    $query = sprintf(' insert into '.$GLOBALS['tables']['listmessage'].' (messageid,listid,entered) values(%d, %d, now())',
                        $msgid, $listid);
                    $result = Sql_Query($query);
                }
            }
        }
    }
    if ($name == 'excludelist' && is_array($value)) {
        ## make sure all entries are numerical. @@TO(DO We could also check that they are actually valid list IDs
        $newLists = array();
        foreach ($value as $v) {
            if (is_numeric($v)) {
                $newLists[$v] = $v;
            }
        }
        $value = $newLists;
        unset($newLists);
    }
    if (is_array($value) || is_object($value)) {
        $value = 'SER:'.serialize($value);
    }
    if ($name == 'footer') {
        // strip HTML comments
        $value = preg_replace('/<!--.*-->/', '', $value); // this is ungreedy
    }

    Sql_Query(sprintf('replace into %s set id = %d,name = "%s", data = "%s"',
        $GLOBALS['tables']['messagedata'], $msgid, addslashes($name), addslashes($value)));
//  print "setting $name for $msgid to $value";
//  exit;
}

function loadMessageData($msgid)
{
    $default = array(
        'from' => getConfig('message_from_address'),
        //# can add some more from below
        'google_track' => getConfig('always_add_googletracking'),
    );
    if (empty($default['from'])) {
        $default['from'] = getConfig('admin_address');
    }

    if (!isset($GLOBALS['MD']) || !is_array($GLOBALS['MD'])) {
        $GLOBALS['MD'] = array();
    }
    if (isset($GLOBALS['MD'][$msgid])) {
        return $GLOBALS['MD'][$msgid];
    }

    //# when loading an old message that hasn't got data stored in message data, load it from the message table
    $prevMsgData = Sql_Fetch_Assoc_Query(sprintf('select * from %s where id = %d',
        $GLOBALS['tables']['message'], $msgid));

    $finishSending = time() + DEFAULT_MESSAGEAGE;

    $messagedata = array(
        'template'       => getConfig('defaultmessagetemplate'),
        'sendformat'     => 'HTML',
        'message'        => '',
        'forwardmessage' => '',
        'textmessage'    => '',
        'rsstemplate'    => '',
        'embargo'        => array(
            'year'   => date('Y'),
            'month'  => date('m'),
            'day'    => date('d'),
            'hour'   => date('H'),
            'minute' => date('i'),
        ),
        'repeatinterval' => 0,
        'repeatuntil'    => array(
            'year'   => date('Y'),
            'month'  => date('m'),
            'day'    => date('d'),
            'hour'   => date('H'),
            'minute' => date('i'),
        ),
        'requeueinterval' => 0,
        'requeueuntil'    => array(
            'year'   => date('Y'),
            'month'  => date('m'),
            'day'    => date('d'),
            'hour'   => date('H'),
            'minute' => date('i'),
        ),
        'finishsending' => array(
            'year'   => date('Y', $finishSending),
            'month'  => date('m', $finishSending),
            'day'    => date('d', $finishSending),
            'hour'   => date('H', $finishSending),
            'minute' => date('i', $finishSending),
        ),
        'fromfield'      => '',
        'subject'        => '',
        'forwardsubject' => '',
        'footer'         => getConfig('messagefooter'),
        'forwardfooter'  => getConfig('forwardfooter'),
        'status'         => '',
        'tofield'        => '',
        'replyto'        => '',
        'targetlist'     => array(),
        'criteria_match' => '',
        'sendurl'        => '',
        'sendmethod'     => 'inputhere', //# make a config
        'testtarget'     => '',
        'notify_start'   => getConfig('notifystart_default'),
        'notify_end'     => getConfig('notifyend_default'),
        'google_track'   => $default['google_track'] == 'true' || $default['google_track'] === true || $default['google_track'] == '1',
        'excludelist'    => array(),
        'sentastest'     => 0,
    );
    if (is_array($prevMsgData)) {
        foreach ($prevMsgData as $key => $val) {
            $messagedata[$key] = $val;
        }
    }

    if (!empty($GLOBALS['commandline']) && $_GET['page'] == 'send') {
        $messagedata['fromfield'] = $_POST['from'];
        $messagedata['subject'] = $_POST['subject'];
        $messagedata['message'] = $_POST['message'];
        $messagedata['targetlist'] = $_POST['targetlist'];
    }
    if ($messagedata['subject'] == '(no title)') {
        $messagedata['subject'] = '(no subject)';
    }

    $msgdata_req = Sql_Query(sprintf('select * from %s where id = %d',
        $GLOBALS['tables']['messagedata'], $msgid));
    while ($row = Sql_Fetch_Assoc($msgdata_req)) {
        if (strpos($row['data'], 'SER:') === 0) {
            $data = stripSlashesArray(unserialize(substr($row['data'], 4)));
        } else {
            $data = stripslashes($row['data']);
        }
        if (!in_array($row['name'],
            array('astext', 'ashtml', 'astextandhtml', 'aspdf', 'astextandpdf'))
        ) { //# don't overwrite counters in the message table from the data table
            $messagedata[stripslashes($row['name'])] = $data;
        }
    }

    foreach (array('embargo', 'repeatuntil', 'requeueuntil') as $datefield) {
        if (!is_array($messagedata[$datefield])) {
            $messagedata[$datefield] = array(
                'year'   => date('Y'),
                'month'  => date('m'),
                'day'    => date('d'),
                'hour'   => date('H'),
                'minute' => date('i'),
            );
        }
    }

    // Load lists that were targetted with message...
    $result = Sql_Query(sprintf('select list.name,list.id
    from ' .$GLOBALS['tables']['listmessage'].' listmessage,'.$GLOBALS['tables']['list'].' list
     where listmessage.messageid = %d and listmessage.listid = list.id', $msgid));
    while ($lst = Sql_fetch_array($result)) {
        $messagedata['targetlist'][$lst['id']] = 1;
    }

    //# backwards, check that the content has a url and use it to fill the sendurl
    if (empty($messagedata['sendurl'])) {

        //# can't do "ungreedy matching, in case the URL has placeholders, but this can potentially
        //# throw problems
        if (!empty($messagedata['message']) && preg_match('/\[URL:(.*)\]/i', $messagedata['message'], $regs)) {
            $messagedata['sendurl'] = $regs[1];
        }
    }
    if (empty($messagedata['sendurl']) && !empty($messagedata['message'])) {
        // if there's a message and no url, make sure to show the editor, and not the URL input
        $messagedata['sendmethod'] = 'inputhere';
    }

    //## parse the from field into it's components - email and name
    if (preg_match('/([^ ]+@[^ ]+)/', $messagedata['fromfield'], $regs)) {
        // if there is an email in the from, rewrite it as "name <email>"
        $messagedata['fromname'] = str_replace($regs[0], '', $messagedata['fromfield']);
        $messagedata['fromemail'] = $regs[0];
        // if the email has < and > take them out here
        $messagedata['fromemail'] = str_replace('<', '', $messagedata['fromemail']);
        $messagedata['fromemail'] = str_replace('>', '', $messagedata['fromemail']);
        // make sure there are no quotes around the name
        $messagedata['fromname'] = str_replace('"', '', ltrim(rtrim($messagedata['fromname'])));
    } elseif (strpos($messagedata['fromfield'], ' ')) {
        // if there is a space, we need to add the email
        $messagedata['fromname'] = $messagedata['fromfield'];
        //  $cached[$messageid]["fromemail"] = "listmaster@$domain";
        $messagedata['fromemail'] = $default['from'];
    } else {
        $messagedata['fromemail'] = $default['from'];
        $messagedata['fromname'] = $messagedata['fromfield'];
    }
    // disallow an email address in the name
    if (preg_match('/([^ ]+@[^ ]+)/', $messagedata['fromname'], $regs)) {
        $messagedata['fromname'] = str_replace($regs[0], '', $messagedata['fromname']);
    }
    // clean up
    $messagedata['fromemail'] = str_replace(',', '', $messagedata['fromemail']);
    $messagedata['fromname'] = str_replace(',', '', $messagedata['fromname']);

    $messagedata['fromname'] = trim($messagedata['fromname']);

    // erase double spacing
    while (strpos($messagedata['fromname'], '  ')) {
        $messagedata['fromname'] = str_replace('  ', ' ', $messagedata['fromname']);
    }

    //# if the name ends up being empty, copy the email
    if (empty($messagedata['fromname'])) {
        $messagedata['fromname'] = $messagedata['fromemail'];
    }

    if (isset($messagedata['targetlist']['unselect'])) {
        unset($messagedata['targetlist']['unselect']);
    }
    if (isset($messagedata['excludelist']['unselect'])) {
        unset($messagedata['excludelist']['unselect']);
    }

    if (empty($messagedata['campaigntitle'])) {
        if ($messagedata['subject'] != '(no subject)') {
            $messagedata['campaigntitle'] = $messagedata['subject'];
        } else {
            $messagedata['campaigntitle'] = '(no title)';
        }
    }
    //# copy subject to title
    if ($messagedata['campaigntitle'] == '(no title)' && $messagedata['subject'] != '(no subject)') {
        $messagedata['campaigntitle'] = $messagedata['subject'];
    }
    $GLOBALS['MD'][$msgid] = $messagedata;
//  var_dump($messagedata);
    return $messagedata;
}

function campaignTitle($id)
{
    $campaignTitle = Sql_Fetch_Assoc_Query(sprintf('select data as title from %s where name = "subject" and id = %d',
        $GLOBALS['tables']['messagedata'], $id));
    if (empty($campaignTitle['title'])) {
        $campaignTitle = Sql_Fetch_Assoc_Query(sprintf('select subject as title from %s where id = %d',
            $GLOBALS['tables']['message'], $id));
    }
    if (empty($campaignTitle['title'])) {
        $campaignTitle['title'] = $id;
    }

    return stripslashes($campaignTitle['title']);
}

//Send an email with a password reset token to the specified adminId.
function sendAdminPasswordToken($adminId)
{
    //Invalidate extisting password reset tokens for this user
    Sql_Query(sprintf("delete from %s where admin = '%s'", $GLOBALS['tables']['admin_password_request'], $adminId));

    //Create random token
    // @TODO alter database schema to allow longer tokens, currently 16 characters / 128 bits
    $key = bin2hex(random_bytes(16));

    // sql to insert token into the database, token can be used to change the password once executed.
    $insertToken = sprintf("insert into %s(date, admin, key_value) values (now(), %d, '%s');",
        $GLOBALS['tables']['admin_password_request'], $adminId, $key);

    //Retrieve the admin login name and email address
    $SQLquery = sprintf('select loginname,email from %s where id=%d;', $GLOBALS['tables']['admin'], $adminId);
    $row = Sql_Fetch_Row_Query($SQLquery);
    $adminName = $row[0];
    $email = $row[1];

    $urlroot = getConfig('website').$GLOBALS['adminpages'];
    //Build the email body to be sent, and finally send it.
    $emailBody = $GLOBALS['I18N']->get('Hello').' '.$adminName."\n\n";
    $emailBody .= $GLOBALS['I18N']->get('You have requested a new password for phpList.')."\n\n";
    $emailBody .= $GLOBALS['I18N']->get('To enter a new one, please visit the following link:')."\n\n";
    $emailBody .= sprintf('%s://%s/?page=login&token=%s', $GLOBALS['admin_scheme'], $urlroot, $key)."\n\n";
    $emailBody .= $GLOBALS['I18N']->get('You have 24 hours left to change your password. After that, your token won\'t be valid.');

    if (sendMail($email, $GLOBALS['I18N']->get('New password'), "\n\n".$emailBody, '', '', true)) {
        //Insert the token into the database
        Sql_Query($insertToken);

        return $GLOBALS['I18N']->get('A password change token has been sent to the corresponding email address.');
    } else {
        return $GLOBALS['I18N']->get('Error sending password change token');
    }
}

function getTopSmtpServer($domain)
{
    $mx = getmxrr($domain, $mxhosts, $weight);
    $thgiew = array_flip($weight);
    ksort($thgiew);

    return $mxhosts[array_shift($thgiew)];
}

function sendMail($to, $subject, $message, $header = '', $parameters = '', $skipblacklistcheck = 0)
{
    if (defined('TEST') && TEST) {
        return 1;
    }

    // do a quick check on mail injection attempt, @@@ needs more work
    if (preg_match("/\n/", $to)) {
        logEvent('Error: invalid recipient, containing newlines, email blocked');

        return 0;
    }
    if (preg_match("/\n/", $subject)) {
        logEvent('Error: invalid subject, containing newlines, email blocked');

        return 0;
    }

    if (!$to) {
        logEvent("Error: empty To: in message with subject $subject to send");

        return 0;
    } elseif (!$subject) {
        logEvent("Error: empty Subject: in message to send to $to");

        return 0;
    }
    if (!$skipblacklistcheck && isBlackListed($to)) {
        logEvent("Error, $to is blacklisted, not sending");
        Sql_Query(sprintf('update %s set blacklisted = 1 where email = "%s"', $GLOBALS['tables']['user'], $to));
        addUserHistory($to, 'Marked Blacklisted',
            'Found user in blacklist while trying to send an email, marked black listed');

        return 0;
    }

    return sendMailPhpMailer($to, $subject, $message);
}

function constructSystemMail($message, $subject = '')
{
    $hasHTML = strip_tags($message) != $message;

    if ($hasHTML) {
        $message = stripslashes($message);
        $textmessage = HTML2Text($message);
        $htmlmessage = $message;
    } else {
        $textmessage = $message;
        $htmlmessage = $message;
        //  $htmlmessage = str_replace("\n\n","\n",$htmlmessage);
        $htmlmessage = nl2br($htmlmessage);
        //# make links clickable:
        $htmlmessage = preg_replace('~https?://[^\s<]+~i', '<a href="$0">$0</a>', $htmlmessage);
    }
    //# add li-s around the lists
    if (preg_match('/<ul>\s+(\*.*)<\/ul>/imsxU', $htmlmessage, $listsmatch)) {
        $lists = $listsmatch[1];
        $listsHTML = '';
        preg_match_all('/\*([^\*]+)/', $lists, $matches);
        for ($i = 0; $i < count($matches[0]); ++$i) {
            $listsHTML .= '<li>'.$matches[1][$i].'</li>';
        }
        $htmlmessage = str_replace($listsmatch[0], '<ul>'.$listsHTML.'</ul>', $htmlmessage);
    }

    $htmlcontent = $htmlmessage;
    $textcontent = $textmessage;
    $templateid = getConfig('systemmessagetemplate');
    if (!empty($templateid)) {
        $req = Sql_Fetch_Row_Query(sprintf('select template, template_text from %s where id = %d',
            $GLOBALS['tables']['template'], $templateid));
        if ($req) {
            $htmltemplate = stripslashes($req[0]);
            $texttemplate = stripslashes($req[1]);
            $htmlcontent = str_replace('[CONTENT]', $htmlmessage, $htmltemplate);
            $htmlcontent = str_replace('[SUBJECT]', $subject, $htmlcontent);
            $htmlcontent = str_replace('[FOOTER]', '', $htmlcontent);
            if (!EMAILTEXTCREDITS) {
                $phpListPowered = preg_replace('/src=".*power-phplist.png"/', 'src="powerphplist.png"',
                    $GLOBALS['PoweredByImage']);
            } else {
                $phpListPowered = $GLOBALS['PoweredByText'];
            }
            if (strpos($htmlcontent, '[SIGNATURE]')) {
                $htmlcontent = str_replace('[SIGNATURE]', $phpListPowered, $htmlcontent);
            } elseif (strpos($htmlcontent, '</body>')) {
                $htmlcontent = str_replace('</body>', $phpListPowered.'</body>', $htmlcontent);
            } else {
                $htmlcontent .= $phpListPowered;
            }
            $htmlcontent = parseLogoPlaceholders($htmlcontent);
            $textcontent = str_replace('[CONTENT]', $textmessage, $texttemplate);
            $textcontent = str_replace('[SUBJECT]', $subject, $textcontent);
            $textcontent = str_replace('[FOOTER]', '', $textcontent);
            $phpListPowered = trim(HTML2Text($GLOBALS['PoweredByText']));
            if (strpos($textcontent, '[SIGNATURE]')) {
                $textcontent = str_replace('[SIGNATURE]', $phpListPowered, $textcontent);
            } else {
                $textcontent .= "\n\n" . $phpListPowered;
            }
        }
    }

    return array($htmlcontent, $textcontent);
}

function sendMailPhpMailer($to, $subject, $message)
{
    // global function to capture sending emails, to avoid trouble with
    // older (and newer!) php versions
    $fromemail = getConfig('message_from_address');
    $fromname = getConfig('message_from_name');
    $message_replyto_address = getConfig('message_replyto_address');
    if ($message_replyto_address) {
        $reply_to = $message_replyto_address;
    } else {
        $reply_to = $from_address;
    }
    $destinationemail = '';

//  print "Sending $to from $fromemail<br/>";
    if (defined('DEVVERSION') && DEVVERSION) {
        $message = "To: $to\n".$message;
        if ($GLOBALS['developer_email']) {
            $destinationemail = $GLOBALS['developer_email'];
        } else {
            //  print 'Error: Running DEV version, but developer_email not set';
        }
    } else {
        $destinationemail = $to;
    }
    list($htmlmessage, $textmessage) = constructSystemMail($message, $subject);

    $mail = new PHPlistMailer('systemmessage', $destinationemail, false);
    if (!empty($htmlmessage)) {
        $mail->add_html($htmlmessage, $textmessage, getConfig('systemmessagetemplate'));
        //# In the above phpMailer strips all tags, which removes the links which are wrapped in < and > by HTML2text
        //# so add it again
        $mail->add_text($textmessage);
    }
    $mail->add_text($textmessage);
    // 0008549: message envelope not passed to php mailer,
    $mail->Sender = $GLOBALS['message_envelope'];

    //# always add the List-Unsubscribe header
    $removeurl = getConfig('unsubscribeurl');
    $sep = strpos($removeurl, '?') === false ? '?' : '&';
    $mail->addCustomHeader('List-Unsubscribe: <'.$removeurl.$sep.'email='.$to.'&jo=1>');

    return $mail->compatSend('', $destinationemail, $fromname, $fromemail, $subject);
}

function sendMailDirect($destinationemail, $subject, $message)
{
    $GLOBALS['smtpError'] = '';
    //# try to deliver directly, so that any error (eg user not found) can be sent back to the
    //# subscriber, so they can fix it
    unset($GLOBALS['developer_email']);

    list($htmlmessage, $textmessage) = constructSystemMail($message, $subject);
    $mail = new PHPlistMailer('systemmessage', $destinationemail, false, true);

    list($dummy, $domain) = explode('@', $destinationemail);

    //print_r ($mxhosts);exit;
    $smtpServer = getTopSmtpServer($domain);

    $fromemail = getConfig('message_from_address');
    $fromname = getConfig('message_from_name');
    $mail->Host = $smtpServer;
    $mail->Helo = getConfig('website');
    $mail->Port = 25;
    $mail->Mailer = 'smtp';
    if (!empty($htmlmessage)) {
        $mail->add_html($htmlmessage, $textmessage, getConfig('systemmessagetemplate'));
        $mail->add_text($textmessage);
    }
    $mail->add_text($textmessage);
    try {
        $mail->send('', $destinationemail, $fromname, $fromemail, $subject);
    } catch (Exception $e) {
        $GLOBALS['smtpError'] = $e->getMessage();

        return false;
    }

    return true;
}

function sendAdminCopy($subject, $message, $lists = array())
{
    $sendcopy = getConfig('send_admin_copies');
    if ($sendcopy) {
        $lists = cleanArray($lists);
        $mails = array();
        if (count($lists) && SEND_LISTADMIN_COPY) {
            $mailsreq = Sql_Query(sprintf('select email from %s admin, %s list where admin.id = list.owner and list.id in (%s)',
                $GLOBALS['tables']['admin'], $GLOBALS['tables']['list'], implode(',', $lists)));
            while ($row = Sql_Fetch_Array($mailsreq)) {
                array_push($mails, $row['email']);
            }
        }
        //# hmm, do we want to be exclusive? Either listadmin or main ones
        //# could do all instead
        if (!count($mails)) {
            $admin_mail = getConfig('admin_address');

            if ($c = getConfig('admin_addresses')) {
                $mails = explode(',', $c);
            }
            array_push($mails, $admin_mail);
        }
        $sent = array();
        foreach ($mails as $admin_mail) {
            $admin_mail = trim($admin_mail);
            if (!isset($sent[$admin_mail]) && !empty($admin_mail)) {
                sendMail($admin_mail, $GLOBALS['installation_name'].' '.$subject, $message, system_messageheaders($admin_mail));
                //   logEvent(s('Sending admin copy to').' '.$admin_mail);
                $sent[$admin_mail] = 1;
            }
        }
    }
}

function safeImageName($name)
{
    $name = 'image'.str_replace('.', 'DOT', $name);
    $name = str_replace('(', 'BRO', $name);
    $name = str_replace(')', 'BRC', $name);
    $name = str_replace(' ', 'SPC', $name);
    $name = str_replace('-', 'DASH', $name);
    $name = str_replace('_', 'US', $name);
    $name = str_replace('/', 'SLASH', $name);
    $name = str_replace(':', 'COLON', $name);

    return $name;
}

function clean2($value)
{
    $value = trim($value);
    $value = preg_replace("/\r/", '', $value);
    $value = preg_replace("/\n/", '', $value);
    $value = str_replace('"', '&quot;', $value);
    $value = str_replace("'", '&rsquo;', $value);
    $value = str_replace('`', '&lsquo;', $value);
    $value = stripslashes($value);

    return $value;
}

function cleanEmail($value)
{
    $value = trim($value);
    $value = preg_replace("/\r/", '', $value);
    $value = preg_replace("/\n/", '', $value);
    $value = preg_replace('/"/', '&quot;', $value);
    $value = preg_replace('/^mailto:/i', '', $value);
    $value = str_replace('(', '', $value);
    $value = str_replace(')', '', $value);
    $value = preg_replace('/\.$/', '', $value);

    //# these are allowed in emails
//  $value = preg_replace("/'/","&rsquo;",$value);
    $value = preg_replace('/`/', '&lsquo;', $value);
    $value = stripslashes($value);

    return $value;
}

if (TEST && REGISTER) {
    $pixel = '<img src="'.PHPLIST_POWEREDBY_URLROOT.'/pixel.gif" width="1" height="1" />';
}

function timeDiff($time1, $time2)
{
    if (!$time1 || !$time2) {
        return $GLOBALS['I18N']->get('Unknown');
    }
    $t1 = strtotime($time1);
    $t2 = strtotime($time2);

    if ($t1 < $t2) {
        $diff = $t2 - $t1;
    } else {
        $diff = $t1 - $t2;
    }
    if ($diff == 0) {
        return $GLOBALS['I18N']->get('very little time');
    }

    return secs2time($diff);
}

function previewTemplate($id, $adminid = 0, $text = '', $footer = '')
{
    global $tables;
    if (defined('IN_WEBBLER')) {
        $more = '&amp;pi='.$_GET['pi'];
    } else {
        $more = '';
    }
    $poweredImageId = 0;
    // make sure the 0 template has the powered by image
    $req = Sql_Query(sprintf('select id from %s where filename = "powerphplist.png" and template = 0',
        $GLOBALS['tables']['templateimage']));
    if (!Sql_Affected_Rows()) {
        Sql_Query(sprintf('insert into %s (template, mimetype, filename, data, width, height)
      values (0, "image/png", "powerphplist.png", "%s", 70, 30)', $GLOBALS['tables']['templateimage'],
            $GLOBALS['newpoweredimage']));
        $poweredImageId = Sql_Insert_Id();
    } else {
        $row = Sql_Fetch_Row($req);
        $poweredImageId = $row[0];
    }
    $tmpl = Sql_Fetch_Row_Query(sprintf('select template from %s where id = %d', $tables['template'], $id));
    $template = stripslashes($tmpl[0]);
    $img_req = Sql_Query(sprintf('select id,filename from %s where template = %d order by filename desc',
        $tables['templateimage'], $id));
    while ($img = Sql_Fetch_Array($img_req)) {
        $template = preg_replace('#'.preg_quote($img['filename']).'#', '?page=image&amp;id='.$img['id'].$more,
            $template);
    }
    if ($adminid) {
        $att_req = Sql_Query("select name,value from {$tables['adminattribute']},{$tables['admin_attribute']} where {$tables['adminattribute']}.id = {$tables['admin_attribute']}.adminattributeid and {$tables['admin_attribute']}.adminid = $adminid");
        while ($att = Sql_Fetch_Array($att_req)) {
            $template = preg_replace("#\[LISTOWNER.".strtoupper(preg_quote($att['name']))."\]#", $att['value'],
                $template);
        }
    }
    if (empty($footer)) {
        $footer = getConfig('messagefooter');
    }

    if ($footer) {
        $template = str_ireplace('[FOOTER]', $footer, $template);
    }
    $template = preg_replace("#\[CONTENT\]#", $text, $template);
    $fromemail = getConfig('campaignfrom_default');
    if (empty($fromemail)) {
        $fromemail = 'user@server.com';
    }
    $template = str_ireplace('[FROMEMAIL]', $fromemail, $template);
    $template = str_ireplace('[EMAIL]', 'recipient@destination.com', $template);

    $template = str_ireplace('[SUBJECT]', s('This is the Newsletter Subject'), $template);
    $template = str_ireplace('[UNSUBSCRIBE]',
        sprintf('<a href="%s">%s</a>', getConfig('unsubscribeurl'), $GLOBALS['strThisLink']), $template);
    //0013076: Blacklisting posibility for unknown users
    $template = str_ireplace('[BLACKLIST]',
        sprintf('<a href="%s">%s</a>', getConfig('blacklisturl'), $GLOBALS['strThisLink']), $template);
    $template = str_ireplace('[PREFERENCES]',
        sprintf('<a href="%s">%s</a>', getConfig('preferencesurl'), $GLOBALS['strThisLink']), $template);

    $logoImageId = getConfig('organisation_logo');
    preg_match_all('/\[LOGO\:?(\d+)?\]/', $template, $logoInstances);
    foreach ($logoInstances[0] as $index => $logoInstance) {
        $size = $logoInstances[1][$index];
        if (!empty($size)) {
            $logoSize = '&amp;m='.$size;
        } else {
            $logoSize = '';
        }
        $template = str_replace($logoInstance, '?page=image&amp;id='.$logoImageId.$logoSize, $template);
    }

    $orgName = getConfig('organisation_name');
    if ($orgName === '' || $orgName === null) {
        // If organisation name is not set, show [ORGANISATION_NAME] placeholder
        $template = str_ireplace('[ORGANISATION_NAME]', '[ORGANISATION_NAME]', $template);
    } else {
        $template = str_ireplace('[ORGANISATION_NAME]', $orgName, $template);
    }

    if (!EMAILTEXTCREDITS) {
        $template = str_ireplace('[SIGNATURE]',
            '<img src="?page=image&amp;id='.$poweredImageId.'" width="70" height="30" />', $template);
    } else {
        $template = str_ireplace('[SIGNATURE]', $GLOBALS['PoweredByText'], $template);
    }
    $template = preg_replace("/\[[A-Z\. ]+\]/", '', $template);
    $template = str_ireplace('<form', '< form', $template);
    $template = str_ireplace('</form', '< /form', $template);

    return $template;
}

function parseMessage($content, $template, $adminid = 0)
{
    global $tables;
    $tmpl = Sql_Fetch_Row_Query("select template from {$tables['template']} where id = $template");
    $template = $tmpl[0];
    $template = preg_replace("#\[CONTENT\]#", $content, $template);
    $att_req = Sql_Query("select name,value from {$tables['adminattribute']},{$tables['admin_attribute']} where {$tables['adminattribute']}.id = {$tables['admin_attribute']}.adminattributeid and {$tables['admin_attribute']}.adminid = $adminid");
    while ($att = Sql_Fetch_Array($att_req)) {
        $template = preg_replace("#\[LISTOWNER.".strtoupper(preg_quote($att['name']))."\]#", $att['value'],
            $template);
    }

    return $template;
}

function listOwner($listid = 0)
{
    global $tables;
    $req = Sql_Fetch_Row_Query("select owner from {$tables['list']} where id = $listid");

    return $req[0];
}

function listUsedInSubscribePage($listid = 0)
{
    if (empty($listid)) {
        return false;
    }
    $used = false;
    $req = Sql_Query(sprintf('select data from %s where name = "lists"', $GLOBALS['tables']['subscribepage_data']));
    while ($row = Sql_Fetch_Assoc($req)) {
        $lists = explode(',', $row['data']);
        $used = $used || in_array($listid, $lists);
        if ($used) {
            return true;
        }
    }

    return $used;
}

function system_messageHeaders($useremail = '')
{
    $from_address = getConfig('message_from_address');
    $from_name = getConfig('message_from_name');
    if ($from_name) {
        $additional_headers = "From: \"$from_name\" <$from_address>\n";
    } else {
        $additional_headers = "From: $from_address\n";
    }
    $message_replyto_address = getConfig('message_replyto_address');
    if ($message_replyto_address) {
        $additional_headers .= "Reply-To: $message_replyto_address\n";
    } else {
        $additional_headers .= "Reply-To: $from_address\n";
    }
    $v = VERSION;
    $additional_headers .= "X-Mailer: phplist version $v (www.phplist.com)\n";
    $additional_headers .= "X-MessageID: systemmessage\n";
    if ($useremail) {
        $additional_headers .= 'X-User: '.$useremail."\n";
    }

    return $additional_headers;
}

function logEvent($msg)
{
    global $tables;
    if (!Sql_Table_Exists($tables['eventlog'])) {
        return;
    }
    $logged = false;
    foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
        $logged = $logged || $plugin->logEvent($msg);
    }
    if ($logged) {
        return;
    }

    if (isset($GLOBALS['page'])) {
        $p = $GLOBALS['page'];
    } elseif (isset($_GET['page'])) {
        $p = $_GET['page'];
    } elseif (isset($_GET['p'])) {
        $p = $_GET['p'];
    } else {
        $p = 'unknown page';
    }
    Sql_Query(sprintf('insert into %s (entered,page,entry) values(now(),"%s","%s")', $tables['eventlog'],
        $p, sql_escape($msg)));
}

//## process locking stuff
function getPageLock($force = 0)
{
    global $tables;
    $thispage = $GLOBALS['page'];
    if ($thispage == 'pageaction') {
        $thispage = $_GET['action'];
    }
    $thispage = preg_replace('/\W/', '', $thispage);
//  cl_output('getting pagelock '.$thispage);
//  ob_end_flush();

    if ($GLOBALS['commandline'] && $thispage == 'processqueue') {
        if (is_object($GLOBALS['MC'])) {
            //# multi-send requires a valid memcached setup
            $max = MAX_SENDPROCESSES;
        } else {
            $max = 1;
        }
    } else {
        $max = 1;
    }

    //# allow killing other processes
    if ($force) {
        Sql_query('delete from '.$tables['sendprocess'].' where page = "'.sql_escape($thispage).'"');
    }

    $running_req = Sql_query(sprintf('select now() - modified as age,id from %s where page = "%s" and alive order by started desc',
        $tables['sendprocess'], sql_escape($thispage)));
    $count = Sql_Num_Rows($running_req);
    $running_res = Sql_Fetch_Assoc($running_req);
    $waited = 0;
    // while ($running_res['age'] && $count >= $max) { # a process is already running
    while ($count >= $max) { // don't check age, as it may be 0
        //   cl_output('running process: '.$running_res['age'].' '.$max);
        if ($running_res['age'] > 600) {
            // some sql queries can take quite a while
            //cl_output($running_res['id'].' is old '.$running_res['age']);
            // process has been inactive for too long, kill it
            Sql_query("update {$tables['sendprocess']} set alive = 0 where id = ".$running_res['id']);
        } elseif ((int) $count >= (int) $max) {
            //   cl_output (sprintf($GLOBALS['I18N']->get('A process for this page is already running and it was still alive %s seconds ago'),$running_res['age']));
            if (function_exists('output')) {
                output(s('A process for this page is already running and it was still alive %d seconds ago', $running_res['age']), 0);
            }
            sleep(1); // to log the messages in the correct order
            if ($GLOBALS['commandline']) {
                cl_output(s('A process for this page is already running and it was still alive %d seconds ago',
                    $running_res['age']), 0);
                cl_output($GLOBALS['I18N']->get('Running commandline, quitting. We\'ll find out what to do in the next run.'));
                exit;
            }
            if (function_exists('output')) {
                output($GLOBALS['I18N']->get('Sleeping for 20 seconds, aborting will quit'), 0);
            }
            flush();
            $abort = ignore_user_abort(0);
            sleep(20);
        }
        ++$waited;
        if ($waited > 10) {
            // we have waited 10 cycles, abort and quit script
            if (function_exists('output')) {
                output($GLOBALS['I18N']->get('We have been waiting too long, I guess the other process is still going ok'), 0);
            }
            return false;
        }
        $running_req = Sql_query('select now() - modified,id from '.$tables['sendprocess']." where page = \"$thispage\" and alive order by started desc");
        $count = Sql_Num_Rows($running_req);
        $running_res = Sql_Fetch_row($running_req);
    }
    if (!empty($GLOBALS['commandline'])) {
        $processIdentifier = SENDPROCESS_SERVERNAME.':'.getmypid();
    } else {
        $processIdentifier = getClientIP();
    }
    $res = Sql_query('insert into '.$tables['sendprocess'].' (started,page,alive,ipaddress) values(now(),"'.$thispage.'",1,"'.$processIdentifier.'")');
    $send_process_id = Sql_Insert_Id();
    $abort = ignore_user_abort(1);
//  cl_output('Got pagelock '.$send_process_id );
    return $send_process_id;
}

function keepLock($processid)
{
    global $tables;
    $thispage = $GLOBALS['page'];
    Sql_query('Update '.$tables['sendprocess']." set alive = alive + 1 where id = $processid");
}

function checkLock($processid)
{
    global $tables;
    $thispage = $GLOBALS['page'];
    $res = Sql_query("select alive from {$tables['sendprocess']} where id = $processid");
    $row = Sql_Fetch_Row($res);

    return $row[0];
}

function getPageCache($url, $lastmodified = 0)
{
    if (empty($_SESSION['hasconf'])) return;
    $req = Sql_Fetch_Row_Query(sprintf('select content from %s where url = "%s" and lastmodified >= %d',
        $GLOBALS['tables']['urlcache'], $url, $lastmodified));
    if (!empty($req) && is_array($req)) {
        return $req[0];
    }
    return '';
}

function getPageCacheLastModified($url)
{
    if (empty($_SESSION['hasconf'])) return;
    $req = Sql_Fetch_Row_Query(sprintf('select lastmodified from %s where url = "%s"', $GLOBALS['tables']['urlcache'],$url));
    if (!empty($req) && is_array($req)) {
        return $req[0];
    }
    return 0;
}

function setPageCache($url, $lastmodified, $content)
{
    if (empty($_SESSION['hasconf'])) return;
    //  if (isset($GLOBALS['developer_email'])) return;
    Sql_Query(sprintf('delete from %s where url = "%s"', $GLOBALS['tables']['urlcache'], $url));
    Sql_Query(sprintf('insert into %s (url,lastmodified,added,content)
    values("%s",%d,now(),"%s")', $GLOBALS['tables']['urlcache'], $url, $lastmodified, sql_escape($content)));
}

function clearPageCache()
{
    unset($GLOBALS['urlcache']);
    Sql_Query('delete from '.$GLOBALS['tables']['urlcache']);
}

function removeJavascript($content)
{
    $content = preg_replace('/<script[^>]*>(.*?)<\/script\s*>/mis', '', $content);
    $content = disableJavascript($content);
    return $content;
}

function stripComments($content)
{
    $content = preg_replace('/<!--(.*?)-->/mis', '', $content);

    return $content;
}

function compressContent($content)
{

    //# this needs loads more testing across systems to be sure
    return $content;

    $content = preg_replace("/\n/", ' ', $content);
    $content = preg_replace("/\r/", '', $content);
    $content = removeJavascript($content);
    $content = stripComments($content);

    //# find some clean way to remove double spacing
    $content = preg_replace("/\t/", ' ', $content);
    while (preg_match('/  /', $content)) {
        $content = preg_replace('/  /', ' ', $content);
    }

    return $content;
}

function encryptPass($pass)
{
    if (empty($pass)) {
        return '';
    }

    if (function_exists('hash')) {
        if (!in_array(HASH_ALGO, hash_algos(), true)) {
            //# fallback, not that secure, but better than none at all
            $algo = 'md5';
        } else {
            $algo = HASH_ALGO;
        }

        return hash($algo, $pass);
    } else {
        return md5($pass);
    }
}

/* do not use @@@

# try to pull remote styles into the email, but that's a nightmare, because
# it needs to be made absolute again
# kind of works, but not really nicely.

function includeStyles($text) {
  $styles = fetchStyles($text);
  $text = stripStyles($text);
  $text = preg_replace('#</head>#','<style type="text/css">'.$styles.'</style></head>',$text);
  return $text;
}

function stripStyles($text) {
  $tags = array('src\s*=\s*','href\s*=\s*','action\s*=\s*',
    'background\s*=\s*','@import\s+','@import\s+url\(');
  foreach ($tags as $tag) {
    preg_match_all('/<link.*('.$tag.')"([^"|\#]*)".*>/Uim', $text, $foundtags);
    for ($i=0; $i< count($foundtags[0]); $i++) {
      $pat = $foundtags[0][$i];
      $match = $foundtags[2][$i];
      $tagmatch = $foundtags[1][$i];
      if (preg_match("#[http|https]:#i",$match) && preg_match("#\.css$#i",$match)) {
        $text = str_replace($foundtags[0][$i],'',$text);
      }
    }
  }
  return $text;
}

function fetchStyles($text) {
  $styles = '';
  $url = '';
  $tags = array('src\s*=\s*','href\s*=\s*','action\s*=\s*',
    'background\s*=\s*','@import\s+','@import\s+url\(');
  foreach ($tags as $tag) {
    preg_match_all('/<link.*('.$tag.')"([^"|\#]*)"/Uim', $text, $foundtags);
    for ($i=0; $i< count($foundtags[0]); $i++) {
      $match = $foundtags[2][$i];
      $url = $match;
      $tagmatch = $foundtags[1][$i];
      if (preg_match("#[http|https]:#i",$match) && preg_match("#\.css$#i",$match)) {
        $styles .= fetchUrl($match);
      }
    }
  }
  return addAbsoluteResources($styles,$url);
}

*/

/* verify that a redirection is to ourselves */
function isValidRedirect($url)
{
    //# we might want to add some more checks here
    return stripos($url, hostName()) || stripos($url,getConfig('website'));
}

/* check the url_append config and expand the url with it
 */

function expandURL($url)
{
    $url_append = getConfig('remoteurl_append');
    $url_append = strip_tags($url_append);
    $url_append = preg_replace('/\W/', '', $url_append);
    if ($url_append) {
        if (strpos($url, '?')) {
            $url = $url.$url_append;
        } else {
            $url = $url.'?'.$url_append;
        }
    }

    return $url;
}

function testUrl($url)
{
    if (VERBOSE) {
        logEvent('Checking '.$url);
    }

    if ($GLOBALS['has_curl']) {
        if (VERBOSE) {
            logEvent('Checking curl ');
        }
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_TIMEOUT, 10);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_DNS_USE_GLOBAL_CACHE, true);
        curl_setopt($curl, CURLOPT_USERAGENT, 'phplist v'.VERSION.'c (https://www.phplist.com)');
        $raw_result = curl_exec($curl);
        $code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    } else {
        if (VERBOSE) {
            logEvent('Checking HTTP_Request2 ');
        }
        require_once 'HTTP/Request2.php';

        $headreq = new HTTP_Request2($url, HTTP_Request2::METHOD_HEAD, array('follow_redirects' => true));
        $headreq->setHeader('User-Agent', 'phplist v'.VERSION.'p (https://www.phplist.com)');

        try {
            $response = $headreq->send();
            $code = $response->getStatus();
        } catch (HTTP_Request2_Exception $e) {
            logEvent(sprintf('Error fetching %s %s', $url, $e->getMessage()));
            $code = 500;
        }
    }
    if (VERBOSE) {
        logEvent('Checking '.$url.' => '.$code);
    }

    return $code;
}

/**
 * Returns the content of a URL from
 *  the global variable $urlcache,
 *  or the urlcache table
 *  or fetching the URL directly.
 *
 * @param string $url      the URL to fetch
 * @param array  $userdata user fields to be replaced in the url
 * @param int    $ttl      time to live, the number of seconds after which the cached item will expire
 *
 * @return string|false the url content or false for an error
 */
function fetchUrl($url, $userdata = array(), $ttl = REMOTE_URL_REFETCH_TIMEOUT)
{
    $content = '';

    //# fix the Editor replacing & with &amp;
    $url = str_ireplace('&amp;', '&', $url);

    // logEvent("Fetching $url");
    if (count($userdata)) {
        foreach ($userdata as $key => $val) {
            if ($key != 'password') {
                $url = utf8_encode(str_ireplace("[$key]", urlencode($val), utf8_decode($url)));
            }
        }
    }

    if (!isset($GLOBALS['urlcache'])) {
        $GLOBALS['urlcache'] = array();
    }

    $url = expandUrl($url);
//  print "<h1>Fetching ".$url."</h1>";

    // keep in memory cache in case we send a page to many emails
    if (isset($GLOBALS['urlcache'][$url]) && is_array($GLOBALS['urlcache'][$url])
        && (time() - $GLOBALS['urlcache'][$url]['fetched'] < $ttl)
    ) {
        //     logEvent($url . " is cached in memory");
        if (VERBOSE && function_exists('output')) {
            output('From memory cache: '.$url);
        }

        return $GLOBALS['urlcache'][$url]['content'];
    }

    $dbcache_lastmodified = getPageCacheLastModified($url);
    $timeout = time() - $dbcache_lastmodified;
    if ($timeout < $ttl) {
        //    logEvent($url.' was cached in database');
        if (VERBOSE && function_exists('output')) {
            output('From database cache: '.$url);
        }

        return getPageCache($url);
    } else {
        //    logEvent($url.' is not cached in database '.$timeout.' '. $dbcache_lastmodified." ".time());
    }

    $request_parameters = array(
        'timeout'        => 600,
        'allowRedirects' => 1,
        'method'         => 'HEAD',
    );

    $remote_charset = 'UTF-8';
    //# relying on the last modified header doesn't work for many pages
    //# use current time instead
    //# see http://mantis.phplist.com/view.php?id=7684
//    $lastmodified = strtotime($header["last-modified"]);
    $lastmodified = time();
    $content = getPageCache($url, $lastmodified);

    if ($content) {
        if (VERBOSE) {
            logEvent($url.' was cached in database');
        }
    } else {
        $content = fetchUrlDirect($url, $request_parameters);
    }

    if (!empty($content)) {
        $content = addAbsoluteResources($content, $url);
        if (VERBOSE) {
            logEvent('Fetching '.$url.' success');
        }
        setPageCache($url, $lastmodified, $content);

        $GLOBALS['urlcache'][$url] = array(
            'fetched' => time(),
            'content' => $content,
        );
    }

    return $content;
}

/**
 * Fetches a URL directly.
 * Use curl if available otherwise fallback to HTTP_Request2.
 *
 * @param string  $url               the URL to fetch
 * @param array   $requestParameters params for the http request
 *
 * @return string|false the url content or false for an error
 */
function fetchUrlDirect($url, $requestParameters = array())
{
    global $has_curl;

    $defaultParameters = array(
        'timeout' => 10,
    );
    $parameters = $requestParameters + $defaultParameters;

    if ($has_curl) {
        return fetchUrlCurl($url, $parameters);
    }

    return fetchUrlHttpRequest2($url, $parameters);
}

function fetchUrlCurl($url, $request_parameters)
{
    if (VERBOSE) {
        logEvent($url.' fetching with curl ');
    }
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_TIMEOUT, $request_parameters['timeout']);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($curl, CURLOPT_HEADER, 0);
    curl_setopt($curl, CURLOPT_DNS_USE_GLOBAL_CACHE, true);
    curl_setopt($curl, CURLOPT_USERAGENT, 'phplist v'.VERSION.'c (https://www.phplist.com)');
    if (HTTP_PROXY_HOST and HTTP_PROXY_PORT) {
        curl_setopt($curl, CURLOPT_PROXY, HTTP_PROXY_HOST);
        curl_setopt($curl, CURLOPT_PROXYPORT, HTTP_PROXY_PORT);
    }
    $raw_result = curl_exec($curl);
    $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    curl_close($curl);
    if (VERBOSE) {
        logEvent('fetched '.$url.' status '.$status);
    }
//    var_dump($status); exit;
    if ($status == 200) {
        return $raw_result;
    } else {
        return '';
    }
}

/**
 * Fetches a URL using the PEAR package HTTP_Request2.
 *
 * @param string  $url               the URL to fetch
 * @param array   $requestParameters params for the http request
 *
 * @return string the url content or false for an error
 */
function fetchUrlHttpRequest2($url, $requestParameters)
{
    require_once 'HTTP/Request2.php';

    if (VERBOSE) {
        logEvent("Fetching $url with HTTP_Request2");
    }
    $request = new HTTP_Request2(
        $url,
        HTTP_Request2::METHOD_GET,
        array(
            'timeout' => $requestParameters['timeout'],
            'follow_redirects' => true,
        )
    );
    $request->setHeader('User-Agent', 'phplist v'.VERSION.'p (http://www.phplist.com)');

    try {
        $response = $request->send();

        if ($response->getStatus() == 200) {
            $content = $response->getBody();

            if (VERBOSE) {
                logEvent("Fetched $url");
            }
        } else {
            logEvent(sprintf('Unexpected HTTP status: %s %s', $response->getStatus(), $response->getReasonPhrase()));
            $content = false;
        }
    } catch (HTTP_Request2_Exception $e) {
        logEvent(sprintf('Error fetching %s %s', $url, $e->getMessage()));
        $content = false;
    }

    return $content;
}

function releaseLock($processid)
{
    global $tables;
    if (!$processid) {
        return;
    }
    Sql_query("delete from {$tables['sendprocess']} where id = $processid");
}

// some basic MM functions, to be expanded upon
function setMaintenanceMode($message = '') {
    SaveConfig('maintenancemode', serialize(array('message' => $message)),0);
}

function clearMaintenanceMode() {
    SaveConfig('maintenancemode','',0);
}

function inMaintenanceMode() {
    $mm = getConfig('maintenancemode');
    return $mm;
}

function parseQueryString($str)
{
    if (empty($str)) {
        return array();
    }
    $op = array();
    $pairs = explode('&', $str);
    foreach ($pairs as $pair) {
        if (strpos($pair, '=') !== false) {
            list($k, $v) = array_map('urldecode', explode('=', $pair));
            $op[$k] = $v;
        } else {
            $op[$pair] = '';
        }
    }

    return $op;
}

function cleanUrl($url, $disallowed_params = array('PHPSESSID'))
{
    // process url only if it contains a disallowed parameter
    $pattern = sprintf('/(%s)=/', implode('|', $disallowed_params));

    if (!preg_match($pattern, $url)) {
        return htmlspecialchars_decode($url);
    }
    $parsed = @parse_url($url);
    $params = array();
    if (empty($parsed['query'])) {
        $parsed['query'] = '';
    }
    // hmm parse_str should take the delimiters as a parameter
    if (strpos($parsed['query'], '&amp;')) {
        $pairs = explode('&amp;', $parsed['query']);
        foreach ($pairs as $pair) {
            if (strpos($pair, '=') !== false) {
                list($key, $val) = explode('=', $pair);
                $params[$key] = $val;
            } else {
                $params[$pair] = '';
            }
        }
    } else {
        //# parse_str turns . into _ which is wrong
//    parse_str($parsed['query'],$params);
        $params = parseQueryString($parsed['query']);
    }
    $uri = !empty($parsed['scheme']) ? $parsed['scheme'].':'.((strtolower($parsed['scheme']) == 'mailto') ? '' : '//') : '';
    $uri .= !empty($parsed['user']) ? $parsed['user'].(!empty($parsed['pass']) ? ':'.$parsed['pass'] : '').'@' : '';
    $uri .= !empty($parsed['host']) ? $parsed['host'] : '';
    $uri .= !empty($parsed['port']) ? ':'.$parsed['port'] : '';
    $uri .= !empty($parsed['path']) ? $parsed['path'] : '';
//  $uri .= $parsed['query'] ? '?'.$parsed['query'] : '';
    $query = '';
    foreach ($params as $key => $val) {
        if (!in_array($key, $disallowed_params)) {
            //0008980: Link Conversion for Click Tracking. no = will be added if key is empty.
            $query .= $key.($val != '' ? '='.$val.'&' : '&');
        }
    }
    $query = substr($query, 0, -1);
    $uri .= $query ? '?'.$query : '';
//  if (!empty($params['p'])) {
//    $uri .= '?p='.$params['p'];
//  }
    $uri .= !empty($parsed['fragment']) ? '#'.$parsed['fragment'] : '';

    return $uri;
}

function adminName($id = 0)
{
    if (!$id) {
        $id = $_SESSION['logindetails']['id'];
    }
    if (is_object($GLOBALS['admin_auth'])) {
        return $GLOBALS['admin_auth']->adminName($id);
    }
    $req = Sql_Fetch_Row_Query(sprintf('select loginname from %s where id = %d', $GLOBALS['tables']['admin'], $id));

    return $req[0] ? $req[0] : 'Nobody';
}

if (!function_exists('dbg')) {
    function xdbg($msg, $logfile = '')
    {
        if (!$logfile) {
            return;
        }
        $fp = @fopen($logfile, 'a');
        $line = '['.date('d M Y, H:i:s').'] '.getenv('REQUEST_URI').'('.$config['stats']['number_of_queries'].") $msg \n";
        @fwrite($fp, $line);
        @fclose($fp);
    }
}

function addSubscriberStatistics($item, $amount, $list = 0)
{
    switch (STATS_INTERVAL) {
        case 'monthly':
            // mark everything as the first day of the month
            $time = mktime(0, 0, 0, date('m'), 1, date('Y'));
            break;
        case 'weekly':
            // mark everything for the first sunday of the week
            $time = mktime(0, 0, 0, date('m'), date('d') - date('w'), date('Y'));
            break;
        case 'daily':
            $time = mktime(0, 0, 0, date('m'), date('d'), date('Y'));
            break;
    }
    Sql_Query(sprintf('update %s set value = value + %d where unixdate = %d and item = "%s" and listid = %d',
        $GLOBALS['tables']['userstats'], $amount, $time, $item, $list));
    $done = Sql_Affected_Rows();
    if (!$done) {
        Sql_Query(sprintf('insert into %s set value = %d,unixdate = %d,item = "%s",listid = %d',
            $GLOBALS['tables']['userstats'], $amount, $time, $item, $list));
    }
}


/**
 * Insert a draft campaign for use with the Invite plugin
 * @param null|int $forcedOwnerId
 * @return bool|mysqli_result
 * @throws Exception
 * @todo Make the campaign content translatable
 * @todo Add Campaign Meta Title to clarify purpose of this draft
 */
function addInviteCampaign($forcedOwnerId = null) {

    $message =
'<p>Hi [FIRST NAME%%there], remember us? You first signed up for our email newsletter on&nbsp;[ENTERED] &ndash; please click here to confirm you&#39;re happy to continue receiving our messages:</p>

<p><strong><a href="[CONFIRMATIONURL]" title="Confirm subscription">Continue receiving messages</a></strong>&nbsp; <u>(If you do not confirm using this link, then you won&#39;t hear from us again</u>)</p>

<p>While you&#39;re at it, you can also update your preferences, including your email address or other details, by clicking here:</p>

<p><strong><a href="[PREFERENCESURL]">Update preferences</a></strong></p>

<p>By confirming your membership and keeping your details up to date, you&#39;re helping us to manage and protect your data in accordance with best practices.</p>

<p>Thank you!</p>';

    $inviteMessage = addslashes($message);
    $inviteMessageSubject = "Do you want to continue receiving our messages?";
    $uuid = uuid::generate(4);
    if ( $forcedOwnerId !== null) {
        $ownerid = $forcedOwnerId;
    } else {
        $ownerid = $_SESSION['logindetails']['id'];
    }
    $footer = sql_escape(getConfig('messagefooter'));
    $result = Sql_query("insert into {$GLOBALS['tables']['message']} (uuid,subject,message,entered, status, owner, footer, sendformat) values(\"$uuid\",\"$inviteMessageSubject\",\"$inviteMessage\",now(),\"draft\",\"$ownerid\",\"$footer\",\"invite\" )");

    return $result;

   }

function deleteMessage($id = 0)
{
    if (isSuperUser()) {
        $ownerselect_and = '';
        $ownerselect_where = '';
    } else {
        $ownerselect_where = ' WHERE owner = '.$_SESSION['logindetails']['id'];
        $ownerselect_and = ' and owner = '.$_SESSION['logindetails']['id'];
    }

    // delete the message in delete
    $result = Sql_query('select id from '.$GLOBALS['tables']['message']." where id = $id $ownerselect_and");
    while ($row = Sql_Fetch_Row($result)) {
        $result = Sql_query('delete from '.$GLOBALS['tables']['message']." where id = $row[0]");
        $suc6 = Sql_Affected_Rows();
        $result = Sql_query('delete from '.$GLOBALS['tables']['usermessage']." where messageid = $row[0]");
        $result = Sql_query('delete from '.$GLOBALS['tables']['listmessage']." where messageid = $row[0]");
        $result = Sql_query('delete from '.$GLOBALS['tables']['user_message_view']." where messageid = $row[0]");
        $result = Sql_query('delete from '.$GLOBALS['tables']['message_attachment']." where messageid = $row[0]");
        $result = Sql_query('delete from '.$GLOBALS['tables']['messagedata']." where id = $row[0]");
        $result = Sql_query('delete from '.$GLOBALS['tables']['linktrack_uml_click']." where messageid = $row[0]");
        $result = Sql_query('delete from '.$GLOBALS['tables']['linktrack_ml']." where messageid = $row[0]");
        $result = Sql_query('delete from '.$GLOBALS['tables']['user_message_bounce']." where message = $row[0]");
        $result = Sql_query('delete from '.$GLOBALS['tables']['user_message_forward']." where message = $row[0]");
        return $suc6;
    }
}

function deleteBounce($id = 0)
{
    if (!$id) {
        return;
    }
    $id = sprintf('%d', $id);
    Sql_query(sprintf('delete from %s where id = %d', $GLOBALS['tables']['bounce'], $id));
    Sql_query(sprintf('delete from %s where bounce = %d', $GLOBALS['tables']['user_message_bounce'], $id));
    Sql_query(sprintf('delete from %s where bounce = %d', $GLOBALS['tables']['bounceregex_bounce'], $id));
}

function reverse_htmlentities($mixed)
{
    $htmltable = get_html_translation_table(HTML_ENTITIES);
    foreach ($htmltable as $key => $value) {
        $mixed = str_replace(addslashes($value), $key, $mixed);
    }

    return $mixed;
}

function loadBounceRules($all = 0)
{
    if ($all) {
        $status = '';
    } else {
        $status = ' where status = "active"';
    }
    $result = array();
    $req = Sql_Query(sprintf('select * from %s %s order by listorder', $GLOBALS['tables']['bounceregex'], $status));
    while ($row = Sql_Fetch_Array($req)) {
        if ($row['regex'] && $row['action']) {
            $result[$row['regex']] = array(
                'action' => $row['action'],
                'id'     => $row['id'],
            );
        }
    }

    return $result;
}

function matchedBounceRule($text, $activeonly = 0)
{
    if ($activeonly) {
        $status = ' where status = "active"';
    } else {
        $status = '';
    }
    $req = Sql_Query(sprintf('select * from %s %s order by listorder', $GLOBALS['tables']['bounceregex'], $status));
    while ($row = Sql_Fetch_Array($req)) {
        $pattern = str_replace(' ', '\s+', $row['regex']);
        //   print "Trying to match ".$pattern;
        //print ' with '.$text;
        //   print '<br/>';
        if (@preg_match('/'.preg_quote($pattern).'/iUm', $text)) {
            return $row['id'];
        } elseif (@preg_match('/'.$pattern.'/iUm', $text)) {
            return $row['id'];
        }
    }

    return '';
}

function matchBounceRules($text, $rules = array())
{
    if (!count($rules)) {
        $rules = loadBounceRules();
    }

    foreach ($rules as $pattern => $rule) {
        $pattern = str_replace(' ', '\s+', $pattern);
        if (@preg_match('/'.preg_quote($pattern).'/iUm', $text)) {
            return $rule;
        } elseif (@preg_match('/'.$pattern.'/iUm', $text)) {
            return $rule;
        } else {
            //      print "Trying to match $pattern failed<br/>";
        }
    }

    return '';
}

function flushBrowser()
{
    //# push some more output to the browser, so it displays things sooner
    for ($i = 0; $i < 10000; ++$i) {
        echo ' '."\n";
    }
    flush();
}

function flushClickTrackCache()
{
    if (!isset($GLOBALS['cached']['linktracksent'])) {
        return;
    }
    foreach ($GLOBALS['cached']['linktracksent'] as $mid => $numsent) {
        foreach ($numsent as $fwdid => $fwdtotal) {
            if (VERBOSE) {
                output("Flushing clicktrack stats for $mid: $fwdid => $fwdtotal");
            }
            Sql_Query(sprintf('update %s set total = %d where messageid = %d and forwardid = %d',
                $GLOBALS['tables']['linktrack_ml'], $fwdtotal, $mid, $fwdid));
        }
    }
}

function resetMessageStatistics($messageid = 0)
{
    //# remove the record of the links in the message, actual clicks of links, and the users sent to

    //# do not do this, if more than X have gone out
    $numsent = Sql_Fetch_Row_Query(sprintf('select count(*) from %s where messageid = %d',
        $GLOBALS['tables']['usermessage'], $messageid));
    if ($numsent[0] < RESETSTATS_MAX) {
        Sql_Query(sprintf('delete from %s where messageid = %d', $GLOBALS['tables']['linktrack_ml'], $messageid));
        Sql_Query(sprintf('delete from %s where messageid = %d', $GLOBALS['tables']['linktrack_uml_click'],
            $messageid));
        Sql_Query(sprintf('delete from %s where messageid = %d', $GLOBALS['tables']['usermessage'], $messageid));
        Sql_Query(sprintf('delete from %s where messageid = %d', $GLOBALS['tables']['user_message_view'], $messageid));
        Sql_Query(sprintf('update %s set viewed = 0 where id = %d', $GLOBALS['tables']['message'], $messageid));
    }
}

if (!function_exists('formatbytes')) {
    function formatBytes($value)
    {
        $gb = 1024 * 1024 * 1024;
        $mb = 1024 * 1024;
        $kb = 1024;
        $gbs = $value / $gb;
        if ($gbs > 1) {
            return sprintf('%2.2fGb', $gbs);
        }
        $mbs = $value / $mb;
        if ($mbs > 1) {
            return sprintf('%2.2fMb', $mbs);
        }
        $kbs = $value / $kb;
        if ($kbs > 1) {
            return sprintf('%dKb', $kbs);
        } else {
            return sprintf('%dBytes', $value);
        }
    }
}

function strip_newlines($str, $placeholder = '')
{
    $str = str_replace(chr(13).chr(10), $placeholder, $str);
    $str = str_replace(chr(10), $placeholder, $str);
    $str = str_replace(chr(13), $placeholder, $str);

    return $str;
}

function parseDate($strdate, $format = 'Y-m-d')
{
    // parse a string date into a date
    $strdate = trim($strdate);
    if (strlen($strdate) < 6) {
        $newvalue = 0;
    } elseif (preg_match("#(\d{2,2}).(\d{2,2}).(\d{4,4})#", $strdate, $regs)) {
        $newvalue = mktime(0, 0, 0, $regs[2], $regs[1], $regs[3]);
    } elseif (preg_match("#(\d{4,4}).(\d{2,2}).(\d{2,2})#", $strdate, $regs)) {
        $newvalue = mktime(0, 0, 0, $regs[2], $regs[3], $regs[1]);
    } elseif (preg_match("#(\d{2,2}).(\w{3,3}).(\d{2,4})#", $strdate, $regs)) {
        $newvalue = strtotime($strdate);
    } elseif (preg_match("#(\d{2,4}).(\w{3,3}).(\d{2,2})#", $strdate, $regs)) {
        $newvalue = strtotime($strdate);
    } else {
        $newvalue = strtotime($strdate);
        if ($newvalue < 0) {
            $newvalue = 0;
        }
    }
    if ($newvalue) {
        return date($format, $newvalue);
    } else {
        return '';
    }
}

function verifyToken()
{
    if (empty($_POST['formtoken'])) {
        return false;
    }

    //# @@@TODO for now ignore the error. This will cause a block on editing admins if the table doesn't exist.
    $req = Sql_Fetch_Row_Query(sprintf('select id from %s where adminid = %d and value = "%s" and expires > now()',
        $GLOBALS['tables']['admintoken'], $_SESSION['logindetails']['id'], sql_escape($_POST['formtoken'])), 1);
    if (empty($req[0])) {
        return false;
    }
    Sql_Query(sprintf('delete from %s where id = %d',
        $GLOBALS['tables']['admintoken'], $req[0]), 1);
    Sql_Query(sprintf('delete from %s where expires < now()',
        $GLOBALS['tables']['admintoken']), 1);

    return true;
}

//# verify the session token on ajaxed GET requests
function verifyCsrfGetToken($enforce = 1)
{ // enforce=0 allows checking "if exist"
    if (!defined('PHPLISTINIT')) {
        die();
    }
    if ($GLOBALS['commandline']) {
        return true;
    }
    if (isset($_GET['tk']) && isset($_SESSION[$GLOBALS['installation_name'].'_csrf_token'])) {
        if (!hash_equals($_SESSION[$GLOBALS['installation_name'].'_csrf_token'], $_GET['tk'])) {
            $_SESSION['logout_error'] = s('Error, incorrect session token');
            Redirect('logout&err=1');
            exit;
        }
    } elseif ($enforce && isset($_SESSION[$GLOBALS['installation_name'].'_csrf_token'])) {
        $_SESSION['logout_error'] = s('Error, incorrect session token');
        Redirect('logout&err=1');
        exit;
    }

    return true;
}

function addCsrfGetToken()
{
    if (empty($_SESSION[$GLOBALS['installation_name'].'_csrf_token'])) {
        $_SESSION[$GLOBALS['installation_name'].'_csrf_token'] = bin2hex(random_bytes(16));
    }

    return '&tk='.$_SESSION[$GLOBALS['installation_name'].'_csrf_token'];
}

function refreshTlds($force = 0)
{
    //# fetch list of Tlds and store in DB
    $lastDone = getConfig('tld_last_sync');
    $tlds = '';
    //# let's not do this too often
    if ($lastDone + TLD_REFETCH_TIMEOUT < time() || $force) {
        //# even if it fails we mark it as done, so that we won't getting stuck in eternal updating.
        SaveConfig('tld_last_sync', time(), 0);
        if (defined('TLD_AUTH_LIST')) {
            $tlds = fetchUrlDirect(TLD_AUTH_LIST);
        }
        if ($tlds && defined('TLD_AUTH_MD5')) {
            $tld_md5 = fetchUrlDirect(TLD_AUTH_MD5);
            list($remote_md5, $fname) = explode(' ', $tld_md5);
            $mymd5 = md5($tlds);
//        print 'OK: '.$remote_md5.' '.$mymd5;
            $validated = $remote_md5 == $mymd5;
        } else {
            $tlds = file_get_contents(dirname(__FILE__).'/data/tlds-alpha-by-domain.txt');
            $validated = true;
        }

        if ($validated) {
            $lines = explode("\n", $tlds);
            $tld_list = '';
            foreach ($lines as $line) {
                //# for now, only handle ascii lines
                if (preg_match('/^\w+$/', $line)) {
                    $tld_list .= $line.'|';
                }
            }
            $tld_list = substr($tld_list, 0, -1);
            SaveConfig('internet_tlds', strtolower($tld_list), 0);
        }
//  } else {
//    print $lastDone;
    }

    return true;
}

function listCategories()
{
    $sListCategories = getConfig('list_categories');
    $aConfiguredListCategories = cleanArray(explode(',', $sListCategories));
    foreach ($aConfiguredListCategories as $key => $val) {
        $aConfiguredListCategories[$key] = trim($val);
    }

    return $aConfiguredListCategories;
}

/*
 * shortenText
 *
 * Shorten text for use by shortenTextDisplay() but also stand-alone.
 *
 * Define multibyte-string aware/unaware function depending on whether the mbstring extension is available
 * see https://github.com/phpList/phplist3/pull/10
 */
if (!function_exists('mb_strlen')) {
    // mbstring unavailable
    function shortenText($text, $max = 30)
    {
        if (strlen($text) > $max) {
            if ($max < 30) {
                $shortened = substr($text, 0, $max - 4).' ... ';
            } else {
                $shortened = substr($text, 0, 20).' ... '.substr($text, -10);
            }
        } else {
            $shortened = $text;
        }

        return $shortened;
    }
} else {
    // mbstring available
    function shortenText($text, $max = 30)
    {
        if (mb_strlen($text) > $max) {
            if ($max < 30) {
                $shortened = mb_substr($text, 0, $max - 4).' ... ';
            } else {
                $shortened = mb_substr($text, 0, 20).' ... '.mb_substr($text, -10);
            }
        } else {
            $shortened = $text;
        }

        return $shortened;
    }
}

/*
 * shortenTextDisplay
 *
 * mostly used for columns in listings to retrict the width, particularly on mobile devices
 * it will show the full text as the title tip but restrict the size of the output
 *
 */
function shortenTextDisplay($text, $max = 30)
{
    $display = shortenText($text, $max);

    return sprintf('<span title="%s">%s</span>', htmlspecialchars($text), $display);
}
/*
 * shortenEmailDisplay
 *
 * Similar to shortenTextDisplay() but adds a wbr element after @ to allow wrapping
 */
function shortenEmailDisplay($text, $max = 30)
{
    $display = shortenText($text, $max);
    $display = str_replace('@', '@<wbr>', $display);

    return sprintf('<span title="%s">%s</span>', htmlspecialchars($text), $display);
}

/*
 * shortenUrlDisplay
 *
 * Similar to shortenTextDisplay() but adds a wbr element after each / to allow wrapping
 */
function shortenUrlDisplay($text, $max = 30)
{
    $display = preg_replace('!^https?://!i', '', $text);
    $display = shortenText($display, $max);
    $display = str_replace('/', '/<wbr>', $display);

    return sprintf('<span title="%s">%s</span>', htmlspecialchars($text), $display);
}

if (!function_exists('getnicebacktrace')) {
    function getNiceBackTrace($bTrace = false)
    {
        $sTrace = '';
        $aBackTrace = debug_backtrace();
        $iMin = 0;
        if ($bTrace) {
            $iMax = count($aBackTrace) - 1;
            $iMax = count($aBackTrace);
        } else {
            $iMax = 3;
        }
        for ($iIndex = $iMin; $iIndex < $iMax; ++$iIndex) {
            if ($bTrace) {
                $sTrace .= "\n";
            }

            $sTrace .= $iIndex.sprintf('%s#%4d:%s() ',
                    pad_right($aBackTrace[$iIndex]['file'], 30),
                    $aBackTrace[$iIndex]['line'],
                    pad_right($aBackTrace[$iIndex]['function'], 15)
                );
        }

        return $sTrace;
    }
}
if (!function_exists('pad_right')) {
    function pad_right($str, $len)
    {
        $str = str_pad($str, $len, ' ', STR_PAD_LEFT);

        return substr($str, strlen($str) - $len, $len);
    }
}

function delFsTree($dir)
{
    if (empty($dir)) {
        return false;
    }
    if (!is_dir($dir) && is_file($dir)) {
        return unlink($dir); //# delete file
    }
    if (!is_dir($dir)) {
        return;
    }
    $files = array_diff(scandir($dir), array('.', '..'));
    foreach ($files as $file) {
        (is_dir("$dir/$file")) ? delFsTree("$dir/$file") : unlink("$dir/$file");
    }

    return rmdir($dir);
}

function copy_recursive($source, $dest)
{
    if (is_dir($source)) {
        mkdir($dest);

        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($source),
            RecursiveIteratorIterator::SELF_FIRST
        );
        foreach ($iterator as $item) {
            if ($item->getBasename() == '..' || $item->getBasename() == '.') {
                continue;
            }
            if ($item->isDir()) {
                mkdir($dest.DIRECTORY_SEPARATOR.$iterator->getSubPathName());
            } else {
                if (!copy($item, $dest.DIRECTORY_SEPARATOR.$iterator->getSubPathName())) {
                    return false;
                }
            }
        }

        return true;
    }

    return copy($source, $dest);
}

function parsePlaceHolders($content, $array = array())
{
    //# the editor turns all non-ascii chars into the html equivalent so do that as well
    foreach ($array as $key => $val) {
        $array[htmlentities($key, ENT_QUOTES, 'UTF-8')] = $val;
        $array[str_ireplace(' ', '&nbsp;', $key)] = $val;
    }

    foreach ($array as $key => $val) {
        if (stripos($content, '['.$key.']') !== false) {
            $content = str_ireplace('['.$key.']', $val, $content);
        }
        $quoted = preg_quote($key, '/');

        if (preg_match('/\['.$quoted.'%%([^\]]+)\]/i', $content, $regs)) {
            if (isset($val) && $val != '') {
                $content = str_ireplace($regs[0], $val, $content);
            } else {
                $content = str_ireplace($regs[0], $regs[1], $content);
            }
        }
    }

    return $content;
}

function quoteEnclosed($value, $col_delim = "\t", $row_delim = "\n")
{
    $enclose = 0;
    if (strpos($value, '"') !== false) {
        $value = str_replace('"', '""', $value);
        $enclose = 1;
    }
    if (strpos($value, $col_delim) !== false) {
        $enclose = 1;
    }
    if (strpos($value, $row_delim) !== false) {
        $enclose = 1;
    }
    if ($enclose) {
        $value = '"'.$value.'"';
    }

    return $value;
}

function activateRemoteQueue()
{
    $result = '';
    $activated = file_get_contents(PQAPI_URL.'&cmd=start&key='.getConfig('PQAPIkey').'&s='.urlencode(getConfig('remote_processing_secret')).'&u='.base64_encode($GLOBALS['admin_scheme'].'://'.hostName().dirname($_SERVER['REQUEST_URI'])));
    if ($activated == 'OK') {
        $result .= '<h3>'.s('Remote queue processing has been activated successfully').'</h3>';
        $result .= '<p>'.PageLinkButton('messages&tab=active', $GLOBALS['I18N']->get('view progress')).'</p>';
    } elseif ($activated == 'KEYFAIL' || $activated == 'NAC') {
        $result .= '<h3>'.s('Error activating remote queue processing').'</h3>';
        if ($activated == 'KEYFAIL') {
            $result .= s('The API key is incorrect');
        } elseif ($activated == 'NAC') {
            $result .= s('The phpList.com server is unable to reach your phpList installation');
        } else {
            $result .= s('Unknown error');
        }
        $result .= '<p><a href="./?page=hostedprocessqueuesetup" class="button">'.s('Change settings').'</a></p>';
        $result .= '<p><a href="./?page=processqueue&pqchoice=local" class="button">'.s('Run queue locally').'</a></p>';
    } else {
        $result .= '<h3>'.s('Error activating remote queue processing').'</h3>';
        $result .= '<p><a href="./?page=processqueue&pqchoice=local" class="button">'.s('Run queue locally').'</a></p>';
    }

    return $result;
}

function subscribeToAnnouncementsForm($emailAddress = '')
{
    if (!is_email($emailAddress) && isset($_SESSION['logindetails']['id'])) {
        $emailAddress = $GLOBALS['admin_auth']->adminEmail($_SESSION['logindetails']['id']);
    }

    return '<div class="information">'
        .'<h3>'.s('Sign up to receive news and updates about phpList ').'</h3>'
        .s('Make sure you are updated with new security and feature release announcements (fewer than one message per month)').
        '<script type="text/javascript">var pleaseEnter = "'.strip_tags($emailAddress).'";</script> '.
        '<script type="text/javascript" src="../js/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/phplist-subscribe-0.3.min.js"></script>
<div id="phplistsubscriberesult"></div> <form action="https://announce.hosted.phplist.com/lists/?p=subscribe&id=3" method="post" id="phplistsubscribeform">
<input type="text" name="email" value="" id="emailaddress" />
<button type="submit" id="phplistsubscribe">' .s('Subscribe').'</button> <button id="phplistnotsubscribe" class="fright">'.s('Do not subscribe').'</button></form>'
        .' </div>';
}

function createCachedLogoImage($size)
{
    $logoImageId = getConfig('organisation_logo');
    if (empty($logoImageId)) {
        return false;
    }

    $imgData = Sql_Fetch_Assoc_Query(sprintf('select data from %s where template = 0 and filename = "ORGANISATIONLOGO%d.png"',
        $GLOBALS['tables']['templateimage'], $size));
    if (!empty($imgData['data'])) {
        return true;
    }
    $imgData = Sql_Fetch_Assoc_Query(sprintf('select data from %s where id = %d and template = 0',
        $GLOBALS['tables']['templateimage'], $logoImageId));
    $imageContent = base64_decode($imgData['data']);
    if (empty($imageContent)) {
        //# fall back to a single pixel, so that there are no broken images
        $imageContent = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABGdBTUEAALGPC/xhBQAAAAZQTFRF////AAAAVcLTfgAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsSAAALEgHS3X78AAAAB3RJTUUH0gQCEx05cqKA8gAAAApJREFUeJxjYAAAAAIAAUivpHEAAAAASUVORK5CYII=');
    }

    if (function_exists('getimagesizefromstring')) {
        $imgSize = getimagesizefromstring($imageContent);
        $sizeW = $imgSize[0];
        $sizeH = $imgSize[1];
        if ($sizeH > $sizeW) {
            $sizefactor = (float) ($size / $sizeH);
        } else {
            $sizefactor = (float) ($size / $sizeW);
        }
        $newwidth = (int) ($sizeW * $sizefactor);
        $newheight = (int) ($sizeH * $sizefactor);
    } else {
        $sizefactor = 1;
    }
    if ($sizefactor < 1) {
        $original = imagecreatefromstring($imageContent);
        $resized = imagecreatetruecolor($newwidth, $newheight); //# creates a black image (why would you want that....)
        imagesavealpha($resized, true);
        $transparent = imagecolorallocatealpha($resized, 255, 255, 255,
            127); //# white. All the methods to make it transparent didn't work for me @@TODO really make transparent
        imagefill($resized, 0, 0, $transparent);

        if (imagecopyresized($resized, $original, 0, 0, 0, 0, $newwidth, $newheight, $sizeW, $sizeH)) {
            Sql_Query(sprintf('delete from %s where template = 0 and filename = "ORGANISATIONLOGO%d.png"',
                $GLOBALS['tables']['templateimage'], $size));

            //# rather convoluted way to get the image contents
            $buffer = ob_get_contents();
            ob_end_clean();
            ob_start();
            imagepng($resized);
            $imageContent = ob_get_contents();
            ob_end_clean();
            echo $buffer;
        }
    } // else copy original
    Sql_Query(sprintf('insert into %s (template,filename,mimetype,data,width,height) values(0,"ORGANISATIONLOGO%d.png","%s","%s",%d,%d)',
        $GLOBALS['tables']['templateimage'], $size, $imgSize['mime'], base64_encode($imageContent), $newwidth,
        $newheight));

    return true;
}

function flushLogoCache()
{
    Sql_Query(sprintf('delete from %s where template = 0 and filename like "ORGANISATIONLOGO%%.png"',
        $GLOBALS['tables']['templateimage']));
}

function parseLogoPlaceholders($content)
{
    //# replace Logo placeholders
    preg_match_all('/\[LOGO\:?(\d+)?\]/', $content, $logoInstances);
    foreach ($logoInstances[0] as $index => $logoInstance) {
        $size = sprintf('%d', $logoInstances[1][$index]);
        if (!empty($size)) {
            $logoSize = $size;
        } else {
            $logoSize = '500';
        }
        createCachedLogoImage($logoSize);
        $content = str_replace($logoInstance, 'ORGANISATIONLOGO'.$logoSize.'.png', $content);
    }

    return $content;
}

/**
 * Parse [CONTACT] as HTML placeholder for VCard
 * @param $content
 * @return mixed
 */
function parseVCardHTMLPlaceholder($content) {
    preg_match_all('/\[CONTACT\:?(\d+)?\]/i', $content, $contactInstances);
    foreach ($contactInstances[0] as $index => $contactInstance) {
        $content = str_ireplace($contactInstance, '<a href="'.htmlentities(getConfig('vcardurl')).'">'.$GLOBALS['strContactMessage'].'</a>', $content);
    }

    return $content;
}

/**
 * Parse [CONTACT] as Text placeholder for VCard
 * @param $content
 * @return mixed
 */
function parseVCardTextPlaceholder($content) {
    preg_match_all('/\[CONTACT\:?(\d+)?\]/i', $content, $contactInstances);
    foreach ($contactInstances[0] as $index => $contactInstance) {
        $content = str_ireplace($contactInstance, $GLOBALS['strContactMessage'].' '.htmlentities(getConfig('vcardurl')), $content);
    }

    return $content;
}

/**
 * Loop through a multi-dimensional array, check a particular child array
 * key equals desired value, and return a new multi-dimensional array of those
 * child arrays which qualify.
 *
 * @param array  $parentArray   Multi-dimensional array to check
 * @param string $requiredKey   Key to check
 * @param string $requiredValue Required value for qualification
 */
function multiArrayFilterBy(array $parentArray, $requiredKey, $requiredValue)
{
    // Initialise empty array for storing qualifying child arrays
    $parentArraySubset = array();
    // Loop through parent arrays
    foreach ($parentArray as $key => $childArray) {
        // Check if child array key value matches required value
        if (arrayKeyHasValue($childArray, $requiredKey, $requiredValue)) {
            // Values match, add to qualified array
            $parentArraySubset[$key] = $childArray;
        }
    }

    return $parentArraySubset;
}

/**
 * Loop through a multi-dimensional array, check a particular child array
 * key **does not equal** desired value, and return a new multi-dimensional array
 * of those child arrays which qualify.
 *
 * @param array  $parentArray    Multi-dimensional array to check
 * @param string $requiredKey    Key to check
 * @param string $forbiddenValue Required value for qualification
 */
function multiArrayFilterByNot(array $parentArray, $requiredKey, $forbiddenValue)
{
    // Initialise empty array for storing qualifying child arrays
    $parentArraySubset = array();
    // Loop through parent arrays
    foreach ($parentArray as $key => $childArray) {
        if (arrayKeyHasNotValue($childArray, $requiredKey, $forbiddenValue)) {
            // Values match, add to qualified array
            $parentArraySubset[$key] = $childArray;
        }
    }

    return $parentArraySubset;
}

/**
 * Check that the value of a given array key matches a particular value.
 *
 * @param array  $array         array to check
 * @param string $requiredKey   Key to check
 * @param string $requiredValue Required value
 */
function arrayKeyHasValue(array $array, $requiredKey, $requiredValue)
{
    // Check if array key value matches required value
    if ($array[$requiredKey] == $requiredValue) {
        return true;
    } else {
        return false;
    }
}

/**
 * Check that the value of a given array key does not match a particular value.
 *
 * @param array  $array          array to check
 * @param string $requiredKey    Key to check
 * @param string $forbiddenValue Forbidden value
 */
function arrayKeyHasNotValue(array $array, $requiredKey, $forbiddenValue)
{
    // Check if array key value matches required value
    if ($array[$requiredKey] != $forbiddenValue) {
        return true;
    } else {
        return false;
    }
}

function asyncLoadContent($url)
{
    return '<script type="text/javascript">
        var loadMessage = \'' .sjs('Please wait, your request is being processed. Do not refresh this page.').'\';
        var loadMessages = new Array();
        loadMessages[30] = \'' .sjs('Still loading').'\';
        loadMessages[90] = \'' .sjs('It may seem to take a while, but there is a lot of data to crunch<br/>if you have a lot of subscribers and campaigns').'\';
        loadMessages[150] = \'' .sjs('It should be soon now, your page content is almost here.').'\';
        loadMessages[210] = \'' .sjs('This seems to take longer than expected, looks like there is a lot of data to work on.').'\';
        loadMessages[240] = \'' .sjs('Still loading, please be patient, your page content will show shortly.').'\';
        loadMessages[300] = \'' .sjs('It will really be soon now until the page will display.').'\';
        loadMessages[360] = \'' .sjs('Still loading, please wait').'\';
        loadMessages[420] = \'' .sjs('The loading has been just over seven minutes. We can wait just a little longer.').'\';
        loadMessages[500] = \'' .sjs('If the page does not load soon, please report this in the user forums.').'\';
        var contentdivcontent = "'.$url.'";
     </script>';
}

/**
 * load content in a div after loading
 */
function asyncLoadContentDiv($url,$divname)
{

    // annoying how associate arrays don't seem to work

    return '<script type="text/javascript">
        if (typeof asyncLoadDiv == "undefined") {
            var asyncLoadDiv = new Array();
            var asyncLoadUrl = new Array();
            var asyncRequestInterval = ' . ASYNC_REQUEST_INTERVAL . ';
        }
        asyncLoadDiv[asyncLoadDiv.length] = "'.$divname.'";
        asyncLoadUrl[asyncLoadUrl.length] = "'.$url.'";
     </script>';
}

/**
 * Transform a value to be valid for an html id by removing invalid characters.
 * This is for HTML 4. HTML 5 is more lenient.
 *
 * @see https://www.w3.org/TR/html4/types.html#type-id
 *
 * @param string $value
 *
 * @return string
 */
function sanitiseId($value)
{
    return preg_replace('/[^0-9A-Za-z\-_:.]/', '', $value);
}

function getClientIP()
{
    static $the_ip = null;

    if ($the_ip !== null) {
        return $the_ip;
    }
    if (function_exists('apache_request_headers')) {
        $headers = apache_request_headers();
        //logEvent("apache_request_headers");
    } else {
        $headers = $_SERVER;
        //logEvent("_SERVER");
    }

    if (array_key_exists('X-Forwarded-For', $headers)) {
        //logEvent("server1=".$headers['X-Forwarded-For']);
    }

    if (array_key_exists('HTTP_X_FORWARDED_FOR', $headers)) {
        //logEvent("server2=".$headers['HTTP_X_FORWARDED_FOR']);
    }

    if (array_key_exists('X-Forwarded-For', $headers)) {
        $forwarded_for = $headers['X-Forwarded-For'];
        $forwarded_list = explode(',', $forwarded_for);
        $forwarded_list = array_map('trim', $forwarded_list);
        $the_ip = array_shift($forwarded_list);

        if (filter_var($the_ip, FILTER_VALIDATE_IP)) {
            //logEvent("X-Forwarded-For ip=".$the_ip);
            return $the_ip;
        }
    }

    if (array_key_exists('HTTP_X_FORWARDED_FOR', $headers)) {
        $forwarded_for = $headers['HTTP_X_FORWARDED_FOR'];
        $forwarded_list = explode(',', $forwarded_for);
        $forwarded_list = array_map('trim', $forwarded_list);
        $the_ip = array_shift($forwarded_list);

        if (filter_var($the_ip, FILTER_VALIDATE_IP)) {
            //logEvent("HTTP_X_FORWARDED_FOR ip=".$the_ip);
            return $the_ip;
        }
    }

    if (isset($_SERVER['REMOTE_ADDR'])) {
        $the_ip = filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP);
    } else {
        $the_ip = '';
    }
    //logEvent("REMOTE_ADDR ip=".$the_ip);

    return $the_ip;
}

Filemanager

Name Type Size Permission Actions
PEAR Folder 0755
PHPMailer Folder 0755
PHPMailer6 Folder 0755
actions Folder 0755
css Folder 0755
data Folder 0755
help Folder 0755
images Folder 0755
inc Folder 0755
info Folder 0755
js Folder 0755
locale Folder 0755
onyxrss Folder 0755
plugins Folder 0755
tests Folder 0755
ui Folder 0755
.gitignore File 20 B 0644
.htaccess File 489 B 0644
.minceconf File 994 B 0644
AnalyticsQuery.php File 985 B 0644
CsvReader.php File 1.27 KB 0644
EmailSender.php File 477 B 0644
Updater.php File 193 B 0644
about.php File 7.4 KB 0644
accesscheck.php File 715 B 0644
addprefix.php File 1.01 KB 0644
adduser.php File 46 B 0644
admin.php File 12.77 KB 0644
adminattributes.php File 7.46 KB 0644
admins.php File 5.16 KB 0644
analytics.php File 2.84 KB 0644
attributes.php File 26.2 KB 0644
blacklistemail.php File 1.22 KB 0644
bounce.php File 11.14 KB 0644
bouncemgt.php File 1.44 KB 0644
bouncerule.php File 4.27 KB 0644
bouncerules.php File 6.33 KB 0644
bounces.php File 7.57 KB 0644
catlists.php File 3.34 KB 0644
checkbouncerules.php File 1.43 KB 0644
checki18n.php File 3.13 KB 0644
checkprerequisites.php File 1.62 KB 0644
class.image.inc File 3.9 KB 0644
class.phplistmailer.php File 30.73 KB 0644
class.phplistmailerbase.php File 1.67 KB 0644
community.php File 3.5 KB 0644
communityfeed.php File 2.36 KB 0644
configure.php File 7.85 KB 0644
connect.php File 89.86 KB 0644
convertstats.php File 5.83 KB 0644
converttoutf8.php File 3.78 KB 0644
cron.php File 3.34 KB 0644
date.php File 7.65 KB 0644
dbcheck.php File 3.7 KB 0644
defaultFrontendTexts.php File 9.79 KB 0644
defaultconfig.php File 30.66 KB 0644
defaultplugin.php File 31.59 KB 0644
defaults.php File 3.64 KB 0644
defaultsystemtemplate.php File 15.29 KB 0644
defaulttest.php File 1.23 KB 0644
dlusers.php File 235 B 0644
domainbounces.php File 507 B 0644
domainstats.php File 371 B 0644
editattributes.php File 8.78 KB 0644
editlist.php File 7.4 KB 0644
eventlog.php File 4.68 KB 0644
export.php File 6.86 KB 0644
exportuserdata.php File 8.26 KB 0644
fckphplist.php File 49.84 KB 0644
gchart.php File 903 B 0644
generatebouncerules.php File 5.51 KB 0644
home.php File 6.56 KB 0644
hostedprocessqueuesetup.php File 3.09 KB 0644
htaccess File 311 B 0644
image.php File 2.01 KB 0644
import.php File 2.75 KB 0644
import1.php File 11.09 KB 0644
import2.php File 34.16 KB 0644
import3.php File 22.72 KB 0644
import4.php File 16.86 KB 0644
importadmin.php File 17.08 KB 0644
importsimple.php File 7.32 KB 0644
index.php File 32.82 KB 0644
info.php File 1.07 KB 0644
init.php File 27.36 KB 0644
initialise.php File 12.05 KB 0644
initlanguages.php File 867 B 0644
languages.php File 21.37 KB 0644
lib.php File 86.79 KB 0644
list.php File 11.32 KB 0644
listbounces.php File 4.13 KB 0644
login.php File 6.39 KB 0644
logout.php File 865 B 0644
massremove.php File 2.55 KB 0644
mclicks.php File 7.28 KB 0644
members.php File 19.99 KB 0644
mergeduplicates.php File 4.48 KB 0644
message.php File 9.08 KB 0644
messages.php File 26.27 KB 0644
minify.txt File 201 B 0644
msgbounces.php File 3.4 KB 0644
msgstatus.php File 1.27 KB 0644
mviews.php File 6.27 KB 0644
mysql.inc File 40 B 0644
mysqli.inc File 14.02 KB 0644
pageaction.php File 1.11 KB 0644
phpListAdminAuthentication.php File 6.82 KB 0644
pluginlib.php File 9.43 KB 0644
plugins.php File 17.78 KB 0644
preparesend.php File 669 B 0644
processbounces.php File 35.36 KB 0644
processqueue.php File 3.71 KB 0644
readtestmail.php File 11.59 KB 0644
reconcileusers.php File 27.71 KB 0644
redirecttoupdater.php File 187 B 0644
reindex.php File 1.82 KB 0644
rsslib.php File 3.17 KB 0644
runcommand.php File 583 B 0644
send.php File 6.17 KB 0644
send_core.php File 63.91 KB 0644
sendemaillib.php File 69.84 KB 0644
sendprepared.php File 4.87 KB 0644
sessionlib.php File 2.7 KB 0644
setpermissions.php File 2.08 KB 0644
setup.php File 2.56 KB 0644
spage.php File 4.35 KB 0644
spageedit.php File 19.08 KB 0644
statsmgt.php File 1.23 KB 0644
statsoverview.php File 6.19 KB 0644
stresstest.php File 4.82 KB 0644
structure.php File 29.21 KB 0644
subscribelib2.php File 70.22 KB 0644
subscriberstats.php File 617 B 0644
suppressionlist.php File 1.71 KB 0644
system.php File 795 B 0644
systemstats.php File 5.73 KB 0644
template.php File 16.4 KB 0644
templates.php File 3.01 KB 0644
tests.php File 1.67 KB 0644
uclicks.php File 6.74 KB 0644
update.php File 187 B 0644
updateLib.php File 2.2 KB 0644
updatetlds.php File 358 B 0644
updatetranslation.php File 2.51 KB 0644
upgrade.php File 23.82 KB 0644
user.php File 23.08 KB 0644
usercheck.php File 2.55 KB 0644
userclicks.php File 11.57 KB 0644
userhistory.php File 8.25 KB 0644
usermgt.php File 1.9 KB 0644
users.php File 19.3 KB 0644
vCard.php File 1.9 KB 0644
viewmessage.php File 635 B 0644
viewtemplate.php File 1.86 KB 0644
vote.php File 38 B 0644