1<?php
2
3require_once dirname(__FILE__).'/inc/userlib.php';
4include_once dirname(__FILE__).'/inc/maillib.php';
5include_once dirname(__FILE__).'/inc/php_compat.php';
6
7// set some variables
8if (!isset($_GET['pi'])) {
9    $_GET['pi'] = '';
10}
11
12$GLOBALS['mail_error'] = '';
13$GLOBALS['mail_error_count'] = 0;
14$organisation_name = getConfig('organisation_name');
15$domain = getConfig('domain');
16$website = getConfig('website');
17if (empty($domain)) {
18    $domain = $_SERVER['SERVER_NAME'];
19}
20if (empty($website)) {
21    $website = $_SERVER['SERVER_NAME'];
22}
23if (empty($organisation_name)) {
24    $organisation_name = $_SERVER['SERVER_NAME'];
25}
26
27$xormask = getConfig('xormask');
28if (empty($xormask)) {
29    $xormask = md5(uniqid(rand(), true));
30    SaveConfig('xormask', $xormask, 0, 1);
31}
32define('XORmask', str_repeat($xormask, 20));
33$hmackey = getConfig('hmackey');
34if (empty($hmackey)) {
35    $hmackey = bin2hex(random_bytes(256));
36    SaveConfig('hmackey', $hmackey, 0, 1);
37}
38define('HMACKEY', $hmackey);
39
40if (empty($_SESSION[$GLOBALS['installation_name'].'_csrf_token'])) {
41    $_SESSION[$GLOBALS['installation_name'].'_csrf_token'] = bin2hex(random_bytes(16));
42}
43if (isset($_SESSION['lastactivity'])) {
44    $_SESSION['session_age'] = time() - $_SESSION['lastactivity'];
45}
46$_SESSION['lastactivity'] = time();
47
48$GLOBALS['img_tick'] = '<span class="yes">Yes</span>';
49$GLOBALS['img_cross'] = '<span class="no">No</span>';
50$GLOBALS['img_view'] = '<span class="view">View</span>';
51$GLOBALS['img_busy'] = '<img src="images/busy.gif" with="34" height="34" border="0" alt="Please wait" id="busyimage" />';
52
53// if keys need expanding with 0-s
54$checkboxgroup_storesize = 1; // this will allow 10000 options for checkboxes
55
56// identify pages that can be run on commandline
57$commandline_pages = array(
58    'initialise',
59    'dbcheck',
60    'send',
61    'processqueue',
62    'processbounces',
63    'import',
64    'upgrade',
65    'convertstats',
66    'reindex',
67    'blacklistemail',
68    'systemstats',
69    'converttoutf8',
70    'initlanguages',
71    'cron',
72    'updatetlds',
73    'export',
74    'runcommand',
75);
76
77if (isset($message_envelope)) {
78    $envelope = "-f$message_envelope";
79}
80
81include_once dirname(__FILE__).'/pluginlib.php';
82
83//# this needs more testing, and docs on how to set the Timezones in the DB
84if (defined('SYSTEM_TIMEZONE')) {
85    //  print('set time_zone = "'.SYSTEM_TIMEZONE.'"<br/>');
86    Sql_Query('set time_zone = "'.SYSTEM_TIMEZONE.'"');
87    //# verify that it applied correctly
88    $tz = Sql_Fetch_Row_Query('select @@session.time_zone');
89    if ($tz[0] != SYSTEM_TIMEZONE) {
90        //# I18N doesn't exist yet, @@TODO need better error catching here
91        echo 'Error setting timezone in Sql Database'.'<br/>';
92    } else {
93        //    print "Mysql timezone set to $tz[0]<br/>";
94    }
95    $phptz_set = date_default_timezone_set(SYSTEM_TIMEZONE);
96    $phptz = date_default_timezone_get();
97    if (!$phptz_set || $phptz != SYSTEM_TIMEZONE) {
98        //# I18N doesn't exist yet, @@TODO need better error catching here
99        echo 'Error setting timezone in PHP'.'<br/>';
100    } else {
101        //    print "PHP system timezone set to $phptz<br/>";
102    }
103//  print "Time now: ".date('Y-m-d H:i:s').'<br/>';
104}
105
106//# build a list of themes that are available
107$themedir = dirname(__FILE__).'/ui';
108$themeNames = array(); // avoid duplicate theme names
109$d = opendir($themedir);
110
111while (false !== ($th = readdir($d))) {
112    if (is_dir($themedir.'/'.$th) && is_file($themedir.'/'.$th.'/theme_info')) {
113        $themeData = parse_ini_file($themedir.'/'.$th.'/theme_info');
114
115        if (false === $themeData || null === $themeData) {
116            // unable to parse the theme info file so choose the first theme found
117            $THEMES[$th] = array(
118                'name' => 'unknown',
119                'dir' => $th,
120            );
121            break;
122        }
123
124        if (!empty($themeData['name']) && !empty($themeData['dir']) && !isset($themeNames[$themeData['name']])) {
125            $THEMES[$th] = $themeData;
126            $themeNames[$themeData['name']] = $th;
127        }
128    }
129}
130if (count($THEMES) > 1 && THEME_SWITCH) {
131    unset($THEMES['default']); // the default theme can be hidden if others are available
132    unset($themeNames['phpList Default']);
133
134    $default_config['UITheme'] = array(
135        'value'       => isset($_SESSION['ui']) ? $_SESSION['ui'] : '',
136        'values'      => array_flip($themeNames),
137        'description' => s('Theme for phpList'),
138        'type'        => 'select',
139        'allowempty'  => false,
140        'category'    => 'general',
141        'hidden'      => false,
142    );
143}
144unset($themeNames);
145
146
147if (!empty($GLOBALS['SessionTableName'])) { // rather undocumented feature, but seems to be used by some
148    include_once dirname(__FILE__).'/sessionlib.php';
149}
150
151if (!isset($table_prefix)) {
152    $table_prefix = '';
153}
154if (!isset($usertable_prefix)) {
155    $usertable_prefix = $table_prefix;
156}
157
158/* set session name, without revealing version
159  * but with version included, so that upgrading works more smoothly
160  */
161
162/* hmm, won't work, going around in circles. Session is started in languages, where the DB
163 * is not known yet, so we can't read xormask from the DB yet*/
164//ini_set('session.name','phpList-'.$GLOBALS['installation_name'].VERSION | $xormask);
165
166$redfont = '';
167$efont = '';
168$GLOBALS['coderoot'] = dirname(__FILE__).'/';
169$GLOBALS['mail_error'] = '';
170$GLOBALS['mail_error_count'] = 0;
171
172function SaveConfig($item, $value, $editable = 1, $ignore_errors = 0)
173{
174    global $tables;
175    //# in case DB hasn't been initialised
176    if (empty($_SESSION['hasconf'])) {
177        $_SESSION['hasconf'] = Sql_Table_Exists($tables['config']);
178    }
179    if (empty($_SESSION['hasconf'])) {
180        return;
181    }
182    if (isset($GLOBALS['default_config'][$item])) {
183        $configInfo = $GLOBALS['default_config'][$item];
184    } else {
185        $configInfo = array(
186            'type'       => 'unknown',
187            'allowempty' => true,
188            'value'      => '',
189        );
190    }
191    //# to validate we need the actual values
192    $value = str_ireplace('[domain]', $GLOBALS['domain'], $value);
193    $value = str_ireplace('[website]', $GLOBALS['website'], $value);
194
195    switch ($configInfo['type']) {
196        case 'boolean':
197            if ($value == 'false' || $value == 'no') {
198                $value = 0;
199            } elseif ($value == 'true' || $value == 'yes') {
200                $value = 1;
201            }
202            break;
203        case 'integer':
204            $value = sprintf('%d', $value);
205            if ($value < $configInfo['min']) {
206                $value = $configInfo['min'];
207            }
208            if ($value > $configInfo['max']) {
209                $value = $configInfo['max'];
210            }
211            break;
212        case 'email':
213            if (!empty($value) && !is_email($value)) {
214                //# hmm, this is displayed only later
215                // $_SESSION['action_result'] = s('Invalid value for email address');
216                return $configInfo['description'].': '.s('Invalid value for email address');
217                $value = '';
218            }
219            break;
220        case 'emaillist':
221            if (!empty($value)) {
222                $valid = array();
223                $hasError = false;
224                $emails = explode(',', $value);
225                foreach ($emails as $email) {
226                    if (is_email($email)) {
227                        $valid[] = $email;
228                    } else {
229                        $hasError = true;
230                    }
231                }
232                $value = implode(',', $valid);
233                /*
234                 * hmm, not sure this is good or bad for UX
235                 *
236                  */
237                if ($hasError) {
238                    return $configInfo['description'].': '.s('Invalid value for email address');
239                }
240            }
241            break;
242        case 'image':
243            include 'class.image.inc';
244            $image = new imageUpload();
245            $imageId = $image->uploadImage($item, 0);
246#            if ($imageId) {
247                $value = $imageId;
248#            }
249            //# we only use the image type for the logo
250            flushLogoCache();
251            break;
252        default:
253            if (isset($configInfo['allowtags'])) { ## allowtags can be set but empty
254                $value = strip_tags($value,$configInfo['allowtags']);
255            }
256            if (isset($configInfo['allowJS']) && !$configInfo['allowJS']) { ## it needs to be set and false
257                $value = disableJavascript($value);
258            }
259    }
260    //# reset to default if not set, and required
261    if (empty($configInfo['allowempty']) && empty($value)) {
262        $value = $configInfo['value'];
263    }
264    if (!empty($configInfo['hidden'])) {
265        $editable = 0;
266    }
267
268    //# force reloading config values in session
269    unset($_SESSION['config']);
270    //# and refresh the config immediately https://mantis.phplist.com/view.php?id=16693
271    unset($GLOBALS['config']);
272
273    Sql_Query(sprintf('replace into %s set item = "%s", value = "%s", editable = %d', $tables['config'],
274        sql_escape($item), sql_escape($value), $editable));
275
276    return false; //# true indicates error, and which one
277}
278
279/*
280  We request you retain the $PoweredBy variable including the links.
281  This not only gives respect to the large amount of time given freely
282  by the developers  but also helps build interest, traffic and use of
283  PHPlist, which is beneficial to it's future development.
284
285  You can configure your PoweredBy options in your config file
286
287  Michiel Dethmers, phpList Ltd 2001-2015
288*/
289if (DEVVERSION) {
290    $v = 'dev';
291} else {
292    $v = VERSION;
293}
294if (REGISTER) {
295    $PoweredByImage = '<p class="poweredby" style="text-align:center"><a href="https://www.phplist.com/poweredby?utm_source=pl'.$v.'&amp;utm_medium=poweredhostedimg&amp;utm_campaign=phpList" title="visit the phpList website" ><img src="'.PHPLIST_POWEREDBY_URLROOT.'/'.$v.'/power-phplist.png" title="powered by phpList version '.$v.', &copy; phpList ltd" alt="powered by phpList '.$v.', &copy; phpList ltd" border="0" /></a></p>';
296} else {
297    $PoweredByImage = '<p class="poweredby" style="text-align:center"><a href="https://www.phplist.com/poweredby?utm_source=pl'.$v.'&amp;utm_medium=poweredlocalimg&amp;utm_campaign=phpList" title="visit the phpList website"><img src="images/power-phplist.png" title="powered by phpList version '.$v.', &copy; phpList ltd" alt="powered by phpList '.$v.', &copy; phpList ltd" border="0"/></a></p>';
298}
299$PoweredByText = '<div style="clear: both; font-family: arial, verdana, sans-serif; font-size: 8px; font-variant: small-caps; font-weight: normal; padding: 2px; padding-left:10px;padding-top:20px;">powered by <a href="https://www.phplist.com/poweredby?utm_source=download'.$v.'&amp;utm_medium=poweredtxt&amp;utm_campaign=phpList" target="_blank" title="powered by phpList version '.$v.', &copy; phpList ltd">phpList</a></div>';
300
301if (!TEST && REGISTER) {
302    if (!PAGETEXTCREDITS) {
303        $PoweredBy = $PoweredByImage;
304    } else {
305        $PoweredBy = $PoweredByText;
306    }
307} else {
308    if (!PAGETEXTCREDITS) {
309        $PoweredBy = $PoweredByImage;
310    } else {
311        $PoweredBy = $PoweredByText;
312    }
313}
314// some other configuration variables, which need less tweaking
315// number of users to show per page if there are more
316if (!defined('MAX_USER_PP')) {
317    define('MAX_USER_PP', 50);
318}
319if (!defined('MAX_MSG_PP')) {
320    define('MAX_MSG_PP', 5);
321}
322// Used by e.g. mviews.php
323if (!defined('MAX_OPENS_PP')) {
324    define('MAX_OPENS_PP', 20);
325}
326
327function formStart($additional = '')
328{
329    global $form_action, $page, $p;
330    // depending on server software we can post to the directory, or need to pass on the page
331    if ($form_action) {
332        $html = sprintf('<form method="post" action="%s" %s>', $form_action, $additional);
333        // retain all get variables as hidden ones
334        foreach (array(
335                     'p',
336                     'page',
337                 ) as $key) {
338            $val = $_REQUEST[$key];
339            if ($val) {
340                $html .= sprintf('<input type="hidden" name="%s" value="%s" />', $key, htmlspecialchars($val));
341            }
342        }
343    } else {
344        $html = sprintf('<form method="post" action="" %s>', $additional);
345    }
346
347    if (!empty($_SESSION['logindetails']['id'])) {
348        //# create the token table, if necessary
349        if (!Sql_Check_For_Table('admintoken')) {
350            createTable('admintoken');
351        }
352        $key = bin2hex(random_bytes(16));
353        Sql_Query(sprintf('insert into %s (adminid,value,entered,expires) values(%d,"%s",%d,date_add(now(),interval 1 hour))',
354            $GLOBALS['tables']['admintoken'], $_SESSION['logindetails']['id'], $key, time()), 1);
355        $html .= sprintf('<input type="hidden" name="formtoken" value="%s" />', $key);
356
357        //# keep the token table empty
358        Sql_Query(sprintf('delete from %s where expires < now()',
359            $GLOBALS['tables']['admintoken']), 1);
360    }
361
362    return $html;
363}
364
365function checkAccess($page, $pluginName = '')
366{
367    if (empty($pluginName)) {
368        if (!$GLOBALS['commandline'] && isset($GLOBALS['disallowpages']) && in_array($page,
369                $GLOBALS['disallowpages'])
370        ) {
371            return 0;
372        }
373    } else {
374        if (!$GLOBALS['commandline'] && isset($GLOBALS['disallowpages']) && in_array($page.'&pi='.$pluginName,
375                $GLOBALS['disallowpages'])
376        ) {
377            return 0;
378        }
379    }
380
381    /*
382      if (isSuperUser())
383        return 1;
384    */
385    //# we allow all that haven't been disallowed
386    //# might be necessary to turn that around
387    return 1;
388}
389
390//@@TODO centralise the reporting and who gets what
391function sendReport($subject, $message)
392{
393    $report_addresses = getConfig('report_address');
394    if ($report_addresses) {
395        foreach (explode(',', $report_addresses) as $address) {
396            sendMail($address, $GLOBALS['installation_name'].' '.$subject, $message);
397        }
398    }
399    foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
400        $plugin->sendReport($GLOBALS['installation_name'].' '.$subject, $message);
401    }
402}
403
404function sendError($message, $to, $subject)
405{
406    foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
407        $plugin->sendError($GLOBALS['installation_name'].' Error: '.$subject, $message);
408    }
409//  Error($msg);
410}
411
412function sendMessageStats($msgid)
413{
414    global $stats_collection_address, $tables;
415    $msg = '';
416    if (defined('NOSTATSCOLLECTION') && NOSTATSCOLLECTION) {
417        return;
418    }
419    if (!isset($stats_collection_address)) {
420        $stats_collection_address = 'phplist-stats@phplist.com';
421    }
422    $data = Sql_Fetch_Array_Query(sprintf('select * from %s where id = %d', $tables['message'], $msgid));
423    $msg .= 'phpList version '.VERSION."\n";
424    $msg .= 'phpList url '.getConfig("website")."\n";
425
426    $diff = timeDiff($data['sendstart'], $data['sent']);
427
428    if ($data['id'] && $data['processed'] > 10 && $diff != 'very little time') {
429        $msg .= "\n".'Time taken: '.$diff;
430        foreach (array(
431                     'entered',
432                     'processed',
433                     'sendstart',
434                     'sent',
435                     'htmlformatted',
436                     'sendformat',
437                     'template',
438                     'astext',
439                     'ashtml',
440                     'astextandhtml',
441                     'aspdf',
442                     'astextandpdf',
443                 ) as $item) {
444            $msg .= "\n".$item.' => '.$data[$item];
445        }
446        sendMail($stats_collection_address, 'phpList stats', $msg, '', '', true);
447    }
448}
449
450function normalize($var)
451{
452    $var = str_replace(' ', '_', $var);
453    $var = str_replace(';', '', $var);
454
455    return $var;
456}
457
458function ClineSignature()
459{
460    return 'phpList version '.VERSION.' (c) 2000-'.date('Y')." phpList Ltd, https://www.phplist.com";
461}
462
463function ClineError($msg)
464{
465    ob_end_clean();
466    echo "\nError: $msg\n";
467    exit;
468}
469
470function clineUsage($line = '')
471{
472    cl_output( 'Usage: '.$_SERVER['SCRIPT_FILENAME']." -p page $line".PHP_EOL);
473}
474
475function Error($msg, $documentationURL = '')
476{
477    if ($GLOBALS['commandline']) {
478        clineError($msg);
479
480        return;
481    }
482    echo '<div class="error">'.$GLOBALS['I18N']->get('error').": $msg ";
483    if (!empty($documentationURL)) {
484        echo resourceLink($documentationURL);
485    }
486    echo '</div>';
487
488    $GLOBALS['mail_error'] .= 'Error: '.$msg."\n";
489    ++$GLOBALS['mail_error_count'];
490    if (is_array($_POST) && count($_POST)) {
491        $GLOBALS['mail_error'] .= "\nPost vars:\n";
492        foreach ($_POST as $key => $val) {
493            if ($key != 'password') {
494                if (is_array($val)) {
495                    $GLOBALS['mail_error'] .= $key.'='.serialize($val)."\n";
496                } else {
497                    $GLOBALS['mail_error'] .= $key.'='.$val."\n";
498                }
499            } else {
500                $GLOBALS['mail_error'] .= "password=********\n";
501            }
502        }
503    }
504}
505
506function clean($value)
507{
508    $value = trim($value);
509    $value = preg_replace("/\r/", '', $value);
510    $value = preg_replace("/\n/", '', $value);
511    $value = str_replace('"', '&quot;', $value);
512    $value = str_replace("'", '&rsquo;', $value);
513    $value = str_replace('`', '&lsquo;', $value);
514    $value = stripslashes($value);
515
516    return $value;
517}
518
519function join_clean($sep, $array)
520{
521    // join values without leaving a , at the end
522    $arr2 = array();
523    foreach ($array as $key => $val) {
524        if ($val) {
525            $arr2[$key] = $val;
526        }
527    }
528
529    return implode($sep, $arr2);
530}
531
532function Fatal_Error($msg, $documentationURL = '')
533{
534    if (empty($_SESSION['fatalerror'])) {
535        $_SESSION['fatalerror'] = 0;
536    }
537    ++$_SESSION['fatalerror'];
538    header('HTTP/1.0 500 Fatal error');
539    if ($_SESSION['fatalerror'] > 5) {
540        $_SESSION['logout_error'] = s('Too many errors, please login again');
541        $_SESSION['adminloggedin'] = '';
542        $_SESSION['logindetails'] = '';
543        session_destroy();
544        Redirect('logout&err=2');
545        exit;
546    }
547
548    if ($GLOBALS['commandline']) {
549        @ob_end_clean();
550        echo "\n".$GLOBALS['I18N']->get('fatalerror').': '.strip_tags($msg)."\n";
551        @ob_start();
552    } else {
553        @ob_end_clean();
554        if (isset($GLOBALS['I18N']) && is_object($GLOBALS['I18N'])) {
555            echo '<div align="center" class="error">'.$GLOBALS['I18N']->get('fatalerror').": $msg ";
556        } else {
557            echo '<div align="center" class="error">'."Fatal Error: $msg ";
558        }
559        if (!empty($documentationURL)) {
560            echo resourceLink($documentationURL);
561        }
562        echo '</div>';
563
564        foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
565            $plugin->processError($msg);
566        }
567    }
568    // include "footer.inc";
569    // exit;
570    return 0;
571}
572
573function resourceLink($url, $title = '')
574{
575    if (empty($title)) {
576        $title = s('Documentation about this error');
577    }
578
579    return ' <span class="resourcelink"><a href="'.$url.'" title="'.htmlspecialchars($title).'" target="_blank" class="resourcelink">'.snbr('More information').'</a></span>';
580}
581
582function Warn($msg)
583{
584    if ($GLOBALS['commandline']) {
585        @ob_end_clean();
586        echo "\n".strip_tags($GLOBALS['I18N']->get('warning').': '.$msg)."\n";
587        @ob_start();
588    } else {
589        echo '<div align=center class="error">'."$msg </div>";
590        $message = '
591
592    An warning has occurred in the Mailinglist System
593
594    ' .$msg;
595    }
596//  sendMail(getConfig("report_address"),"Mail list warning",$message,"");
597}
598
599function Info($msg, $noClose = false)
600{
601    if (!empty($GLOBALS['commandline'])) {
602        @ob_end_clean();
603        echo "\n".strip_tags($msg)."\n";
604        @ob_start();
605    } else {
606        //# generate some ID for the info div
607        $id = substr(md5($msg), 0, 15);
608        $pageinfo = new pageInfo($id);
609        $pageinfo->setContent('<p>'.$msg.'</p>');
610        if ($noClose && method_exists($pageinfo, 'suppressHide')) {
611            $pageinfo->suppressHide();
612        }
613        echo $pageinfo->show();
614    }
615}
616
617function ActionResult($msg)
618{
619    if ($GLOBALS['commandline']) {
620        @ob_end_clean();
621        echo "\n".strip_tags($msg)."\n";
622        @ob_start();
623    } else {
624        return '<div class="actionresult">'.$msg.'</div>';
625    }
626}
627
628function pageTitle($page)
629{
630    return $GLOBALS['I18N']->pageTitle($page);
631}
632
633$GLOBALS['pagecategories'] = array(
634    //# category title => array(
635    // toplink => page to link top menu to
636    // pages => pages in this category
637    'dashboard'   => array(
638        'toplink'=> 'home',
639        'pages'  => array(),
640        'menulinks' => array(),
641    ),
642    'subscribers' => array(
643        'toplink' => 'list',
644        'pages'   => array(
645            'users',
646            'usermgt',
647            'members',
648            'import',
649            'import1',
650            'import2',
651            'import3',
652            'import4',
653            'importsimple',
654            'dlusers',
655            'export',
656            'listbounces',
657            'massremove',
658            'suppressionlist',
659            'reconcileusers',
660            'usercheck',
661            'user',
662            'adduser',
663            'attributes',
664
665        ),
666        'menulinks' => array(
667            'users',
668            'usermgt',
669            'attributes',
670            'list',
671            'import',
672            'export',
673            'listbounces',
674            'suppressionlist',
675            'reconcileusers',
676        ),
677
678    ),
679    'campaigns' => array(
680        'toplink' => 'messages',
681        'pages'   => array(
682            'send',
683            'sendprepared',
684            'message',
685            'messages',
686            'viewmessage',
687            'templates',
688            'template',
689            'viewtemplate',
690        ),
691        'menulinks' => array(
692            'send',
693            'messages',
694            'templates',
695        ),
696    ),
697    'statistics' => array(
698        'toplink' => 'statsmgt',
699        'pages'   => array(
700            'mviews',
701            'mclicks',
702            'uclicks',
703            'userclicks',
704            'statsmgt',
705            'statsoverview',
706            'domainstats',
707            'msgbounces',
708        ),
709        'menulinks' => array(
710            'statsoverview',
711            'mviews',
712            'mclicks',
713            'uclicks',
714            'domainstats',
715            'msgbounces',
716        ),
717    ),
718    'system' => array(
719        'toplink' => 'system',
720        'pages'   => array(
721            'bounce',
722            'bounces',
723            'convertstats',
724            'dbcheck',
725            'eventlog',
726            'bouncemgt',
727            'generatebouncerules',
728            'initialise',
729            'upgrade',
730            'processqueue',
731            'processbounces',
732            'reindex',
733            'resetstats',
734            'updatetranslation',
735        ),
736        'menulinks' => array(
737            //     'bounces',
738            'updatetranslation',
739            'dbcheck',
740            'eventlog',
741            'initialise',
742            'upgrade',
743            'bouncemgt',
744            'processqueue',
745            //     'processbounces',
746            'reindex',
747        ),
748    ),
749    'config' => array(
750        'toplink' => 'setup',
751        'pages'   => array(
752            'setup',
753            'configure',
754            'plugins',
755            'catlists',
756            'spage',
757            'spageedit',
758            'admins',
759            'admin',
760            'importadmin',
761            'adminattributes',
762            'editattributes',
763            'defaults',
764            'bouncerules',
765            'bouncerule',
766            'checkbouncerules',
767        ),
768        'menulinks' => array(
769            'setup',
770            'configure',
771            'plugins',
772            'spage',
773            'admins',
774            'importadmin',
775            'adminattributes',
776            'bouncerules',
777            'checkbouncerules',
778            'catlists',
779        ),
780    ),
781    //'info' => array(
782        //'toplink' => 'about',
783        //'pages'   => array(
784            //'about',
785            //'community',
786            //'home',
787            //   'translate',
788            //'vote',
789        //),
790        //'menulinks' => array(
791           // 'about',
792            //'community',
793            //    'translate',
794            //'home',
795        //),
796    //),
797
798    //'plugins' => array(
799    //'toplink' => 'plugins',
800    //'pages' => array(),
801    //'menulinks' => array(),
802    //),
803);
804if (isSuperUser() && ALLOW_UPDATER) {
805    $GLOBALS['pagecategories']['system']['pages'][] = 'update';
806    $GLOBALS['pagecategories']['system']['menulinks'][] = 'update';
807}
808if (DEVVERSION) {
809    $GLOBALS['pagecategories']['develop'] = array(
810        'toplink' => 'develop',
811        'pages'   => array(
812            //   'checki18n',
813            'stresstest',
814            'subscriberstats',
815            'tests',
816        ),
817        'menulinks' => array(
818            //   'checki18n',
819            'stresstest',
820            'subscriberstats',
821            'tests',
822        ),
823    );
824}
825function pageCategory($page)
826{
827    foreach ($GLOBALS['pagecategories'] as $category => $cat_details) {
828        if (in_array($page, $cat_details['pages'])) {
829            return $category;
830        }
831    }
832
833    return '';
834}
835
836/*
837$main_menu = array(
838  "configure" => "Configure",
839  "community" => "Help",
840  "about" => "About",
841  "div1" => "<hr />",
842  "list" => "Lists",
843  "send"=>"Send a message",
844  "users" => "Users",
845  "usermgt" => "Manage Users",
846  "spage" => "Subscribe Pages",
847  "messages" => "Messages",
848  'statsmgt' => 'Statistics',
849  "div2" => "<hr />",
850  "templates" => "Templates",
851  "preparesend"=>"Prepare a message",
852  "sendprepared"=>"Send a prepared message",
853  "processqueue"=>"Process Queue",
854  "processbounces"=>"Process Bounces",
855  "bouncemgt" => 'Manage Bounces',
856  "bounces"=>"View Bounces",
857  "eventlog"=>"Eventlog"
858);
859*/
860$GLOBALS['context_menu'] = array(
861    'home'      => 'home',
862    'community' => 'help',
863    'about'     => 'about',
864    'logout'    => 'logout',
865);
866
867function contextMenu()
868{
869    if (isset($GLOBALS['firsttime']) || (isset($_GET['page']) && $_GET['page'] == 'initialise')) {
870        return;
871    }
872    if (!CLICKTRACK) {
873        unset($GLOBALS['context_menu']['statsmgt']);
874    }
875    $shade = 1;
876    $spb = '<li class="shade0">';
877//  $spb = '<li class="shade2">';
878    $spe = '</li>';
879    $nm = mb_strtolower(NAME);
880    if ($nm != 'phplist') {
881        $GLOBALS['context_menu']['community'] = '';
882    }
883    // if (USE_ADVANCED_BOUNCEHANDLING) {
884    $GLOBALS['context_menu']['bounces'] = '';
885    $GLOBALS['context_menu']['processbounces'] = '';
886    // } else {
887    //   $GLOBALS["context_menu"]["bouncemgt"] = '';
888    // }
889
890    if (!isset($_SESSION['adminloggedin']) || !$_SESSION['adminloggedin']) {
891        return '<ul class="contextmenu">'.$spb.PageLink2('home',
892            $GLOBALS['I18N']->get('Main Page')).'<br />'.$spe.$spb.PageLink2('about',
893            $GLOBALS['I18N']->get('about').' phplist').'<br />'.$spe.'</ul>';
894    }
895
896    $access = accessLevel('spage');
897    switch ($access) {
898        case 'owner':
899            $subselect = sprintf(' where owner = %d', $_SESSION['logindetails']['id']);
900            break;
901        case 'all':
902        case 'view':
903            $subselect = '';
904            break;
905        case 'none':
906        default:
907            $subselect = ' where id = 0';
908            break;
909    }
910    if (TEST && REGISTER) {
911        $pixel = '<img src="https://d3u7tsw7cvar0t.cloudfront.net/images/pixel.gif" width="1" height="1" alt="" />';
912    } else {
913        $pixel = '';
914    }
915    global $tables;
916    $html = '';
917
918    if (isset($_GET['page'])) {
919        $thispage = $_GET['page'];
920    } else {
921        $thispage = 'home';
922    }
923    $thispage_category = pageCategory($thispage);
924
925    if (empty($thispage_category) && empty($_GET['pi'])) {
926        $thispage_category = '';
927    } elseif (!empty($_GET['pi'])) {
928        $thispage_category = 'plugins';
929    }
930
931    if (!empty($thispage_category) && !empty($GLOBALS['pagecategories'][$thispage_category]['menulinks'])) {
932        if (count($GLOBALS['pagecategories'][$thispage_category]['menulinks'])) {
933            foreach ($GLOBALS['pagecategories'][$thispage_category]['menulinks'] as $category_page) {
934                $GLOBALS['context_menu'][$category_page] = $category_page;
935            }
936        } else {
937            unset($GLOBALS['context_menu']['categoryheader']);
938        }
939    } elseif (!empty($_GET['pi'])) {
940        if (isset($GLOBALS['plugins'][$_GET['pi']]) && method_exists($GLOBALS['plugins'][$_GET['pi']], 'adminmenu')) {
941            $GLOBALS['context_menu']['categoryheader'] = $GLOBALS['plugins'][$_GET['pi']]->name;
942            $GLOBALS['context_menu'] = $GLOBALS['plugins'][$_GET['pi']]->adminMenu();
943        }
944    }
945
946    foreach ($GLOBALS['context_menu'] as $page => $desc) {
947        if (!$desc) {
948            continue;
949        }
950        $link = PageLink2($page, $GLOBALS['I18N']->pageTitle($desc));
951        if ($link) {
952            if ($page == 'preparesend' || $page == 'sendprepared') {
953                if (USE_PREPARE) {
954                    $html .= $spb.$link.$spe;
955                }
956            } // don't use the link for a rule
957            elseif ($desc == '<hr />') {
958                $html .= '<li>'.$desc.'</li>';
959            } elseif ($page == 'categoryheader') {
960                //  $html .= '<li><h3>'.$GLOBALS['I18N']->get($thispage_category).'</h3></li>';
961                $html .= '<li><h3>'.$GLOBALS['I18N']->get('In this section').'</h3></li>';
962            } else {
963                $html .= $spb.$link.$spe;
964            }
965        }
966    }
967    /*
968      if (sizeof($GLOBALS["plugins"])) {
969        $html .= $spb."<hr/>".$spe;
970        foreach ($GLOBALS["plugins"] as $pluginName => $plugin) {
971          $html .= $spb.PageLink2("main&amp;pi=$pluginName",$pluginName).$spe;
972        }
973      }
974    */
975
976    if ($html) {
977        return '<ul class="contextmenu">'.$html.'</ul>'.$pixel;
978    } else {
979        return '';
980    }
981}
982
983function recentlyVisited()
984{
985    $html = '';
986    if (!isset($_SESSION['browsetrail']) || !is_array($_SESSION['browsetrail'])) {
987        $_SESSION['browsetrail'] = array();
988    }
989    if (empty($_SESSION['adminloggedin'])) {
990        return '';
991    }
992    if (isset($_SESSION['browsetrail']) && is_array($_SESSION['browsetrail'])) {
993        if (!empty($_COOKIE['browsetrail'])) {
994            //      if (!in_array($_COOKIE['browsetrail'],$_SESSION['browsetrail'])) {
995            array_unshift($_SESSION['browsetrail'], $_COOKIE['browsetrail']);
996//      }
997        }
998
999        $shade = 0;
1000        $html .= '<h3>'.$GLOBALS['I18N']->get('Recently visited').'</h3><ul class="recentlyvisited">';
1001        $browsetrail = array_unique($_SESSION['browsetrail']);
1002
1003//    $browsetrail = array_reverse($browsetrail);
1004        $browsetaildone = array();
1005        $num = 0;
1006        foreach ($browsetrail as $pageid => $visitedpage) {
1007            if (strpos($visitedpage,
1008                'SEP')) { //# old method, store page title in cookie. However, that breaks on multibyte languages
1009                list($pageurl, $pagetitle) = explode('SEP', strip_tags($visitedpage));
1010                if ($pagetitle != 'phplist') {  //# pages with no title
1011//          $pagetitle = str_replace('%',' ',$pagetitle);
1012                    if (strpos($pagetitle, ' ') > 20) {
1013                        $pagetitle = substr($pagetitle, 0, 10).' ...';
1014                    }
1015                    $html .= '<li class="shade'.$shade.'"><a href="./'.$pageurl.'" title="'.htmlspecialchars($pagetitle).'"><!--'.$pageid.'-->'.$pagetitle.'</a></li>';
1016                    $shade = !$shade;
1017                }
1018            } else {
1019                if (@preg_match('/\?page=([\w]+)/', $visitedpage, $regs)) {
1020                    $p = $regs[1];
1021                    $urlparams = array();
1022                    $pairs = explode('&', $visitedpage);
1023                    foreach ($pairs as $pair) {
1024                        if (strpos($pair, '=')) {
1025                            list($var, $val) = explode('=', $pair);
1026                            $urlparams[$var] = $val;
1027                        }
1028                    }
1029                    //# pass on ID
1030                    if (isset($urlparams['id'])) {
1031                        $urlparams['id'] = sprintf('%d', $urlparams['id']);
1032                    }
1033                    $url = 'page='.$p;
1034                    if (!empty($urlparams['id'])) {
1035                        $url .= '&id='.$urlparams['id'];
1036                    }
1037                    //# check for plugin
1038                    if (isset($urlparams['pi']) && isset($GLOBALS['plugins'][$urlparams['pi']])) {
1039                        $url .= '&pi='.$urlparams['pi'];
1040                        $title = $GLOBALS['plugins'][$urlparams['pi']]->pageTitle($p);
1041                        $titlehover = $GLOBALS['plugins'][$urlparams['pi']]->pageTitleHover($p);
1042                    } else {
1043                        unset($urlparams['pi']);
1044                        $title = $GLOBALS['I18N']->pageTitle($p);
1045                        $titlehover = $GLOBALS['I18N']->pageTitleHover($p);
1046                    }
1047                    if (!empty($p) && !empty($title) && !in_array($url, $browsetaildone)) {
1048                        $html .= '<li class="shade'.$shade.'"><a href="./?'.htmlspecialchars($url).addCsrfGetToken().'" title="'.htmlspecialchars($titlehover).'"><!--'.$pageid.'-->'.$title.'</a></li>';
1049                        $shade = !$shade;
1050                        $browsetaildone[] = $url;
1051                        ++$num;
1052                    }
1053                }
1054            }
1055            if ($num >= 3) {
1056                break;
1057            }
1058        }
1059
1060        $html .= '</ul>';
1061        $_SESSION['browsetrail'] = array_slice($_SESSION['browsetrail'], 0, 20);
1062    }
1063
1064    return $html;
1065}
1066
1067function topMenu()
1068{
1069    if (empty($_SESSION['logindetails'])) {
1070        return '';
1071    }
1072
1073    if ($_SESSION['logindetails']['superuser']) { // we don't have a system yet to distinguish access to plugins
1074        if (count($GLOBALS['plugins'])) {
1075            foreach ($GLOBALS['plugins'] as $pluginName => $plugin) {
1076                //if (isset($GLOBALS['pagecategories']['plugins'])) {
1077                //array_push($GLOBALS['pagecategories']['plugins']['menulinks'],'main&pi='.$pluginName);
1078                //}
1079                $menulinks = $plugin->topMenuLinks;
1080                foreach ($menulinks as $link => $linkDetails) {
1081                    if (isset($GLOBALS['pagecategories'][$linkDetails['category']])) {
1082                        array_push($GLOBALS['pagecategories'][$linkDetails['category']]['menulinks'],
1083                            $link.'&pi='.$pluginName);
1084                    }
1085                }
1086            }
1087        }
1088    }
1089
1090    $topmenu = '';
1091    $topmenu .= '<div id="menuTop">';
1092    if (!DEVVERSION) {
1093        unset($GLOBALS['pagecategories']['develop']);
1094    }
1095
1096    foreach ($GLOBALS['pagecategories'] as $category => $categoryDetails) {
1097        if ($category == 'hide'
1098            //# hmm, this also suppresses the "dashboard" item
1099            //     || count($categoryDetails['menulinks']) == 0
1100        ) {
1101            continue;
1102        }
1103
1104        $thismenu = '';
1105        foreach ($categoryDetails['menulinks'] as $page) {
1106            $title = $GLOBALS['I18N']->pageTitle($page);
1107
1108            $link = PageLink2($page, $title, '', true);
1109            if ($link) {
1110                $thismenu .= '<li>'.$link.'</li>';
1111            }
1112        }
1113        if (!empty($thismenu)) {
1114            $thismenu = '<ul>'.$thismenu.'</ul>';
1115        }
1116
1117        if (!empty($categoryDetails['toplink'])) {
1118            $categoryurl = PageUrl2($categoryDetails['toplink'], '', '', true);
1119            if ($categoryurl) {
1120                $topmenu .= '<ul><li><a href="'.$categoryurl.'" title="'.$GLOBALS['I18N']->pageTitleHover($category).'">'.ucfirst($GLOBALS['I18N']->get($category)).'</a>'.$thismenu.'</li></ul>';
1121            } else {
1122                $topmenu .= '<ul><li><span>'.$GLOBALS['I18N']->get($category).$categoryurl.'</span>'.$thismenu.'</li></ul>';
1123            }
1124        }
1125    }
1126
1127    $topmenu .= '</div>';
1128
1129    return $topmenu;
1130}
1131
1132//## hmm, these really should become objects
1133function PageLink2($name, $desc = '', $url = '', $no_plugin = false, $title = '')
1134{
1135    $plugin = '';
1136    if ($url) {
1137        $url = '&amp;'.$url;
1138    }
1139
1140    if (in_array($name, $GLOBALS['disallowpages'])) {
1141        return '';
1142    }
1143    if (strpos($name, '&') !== false) {
1144        preg_match('/([^&]+)&/', $name, $regs);
1145        $page = $regs[1];
1146        if (preg_match('/&pi=([^&]+)/', $name, $regs)) {
1147            $plugin = $regs[1];
1148        }
1149        if (in_array($page, $GLOBALS['disallowpages'])) {
1150            return '';
1151        }
1152    } else {
1153        $page = $name;
1154    }
1155
1156    $access = accessLevel($page);
1157    if (empty($plugin) || !is_object($GLOBALS['plugins'][$plugin])) {
1158        $name = str_replace('&amp;', '&', $name);
1159        $name = str_replace('&', '&amp;', $name);
1160    } else {
1161        if (isset($GLOBALS['plugins'][$plugin]->pageTitles[$page])) {
1162            $desc = $GLOBALS['plugins'][$plugin]->pageTitles[$page];
1163        } else {
1164            $desc = $plugin.' - '.$page;
1165        }
1166    }
1167
1168    if (empty($desc)) {
1169        $desc = $name;
1170    }
1171    if (empty($title)) {
1172        $title = $GLOBALS['I18N']->pageTitleHover($page);
1173        if (empty($title)) {
1174            $title = $desc;
1175        }
1176    }
1177
1178    $pqChoice = getConfig('pqchoice');
1179    $hideProcessQueue = !MANUALLY_PROCESS_QUEUE;
1180
1181    if ($access == 'owner' || $access == 'all' || $access == 'view') {
1182        if ($name == 'processqueue' && $hideProcessQueue) {
1183            return '';
1184        }//'<!-- '.$desc.'-->';
1185        elseif ($name == 'processbounces' && !MANUALLY_PROCESS_BOUNCES) {
1186            return '';
1187        } //'<!-- '.$desc.'-->';
1188        else {
1189            if (!$no_plugin && !preg_match('/&amp;pi=/i',
1190                    $name) && isset($_GET['pi']) && isset($GLOBALS['plugins'][$_GET['pi']]) && is_object($GLOBALS['plugins'][$_GET['pi']])
1191            ) {
1192                $pi = '&amp;pi='.$_GET['pi'];
1193            } else {
1194                $pi = '';
1195            }
1196
1197            if (!empty($_SESSION[$GLOBALS['installation_name'].'_csrf_token'])) {
1198                $token = '&amp;tk='.$_SESSION[$GLOBALS['installation_name'].'_csrf_token'];
1199            } else {
1200                $token = '';
1201            }
1202            $linktext = $desc;
1203            $linktext = str_ireplace('phplist', 'phpList', $linktext);
1204
1205            return sprintf('<a href="./?page=%s%s%s%s" title="%s">%s</a>', $name, $url, $pi, $token,
1206                htmlspecialchars(strip_tags($title)), $linktext);
1207        }
1208    }
1209
1210    return '';
1211//    return "\n<!--$name disabled $access -->\n";
1212//    return "\n$name disabled $access\n";
1213}
1214
1215//# hmm actually should rename to PageLinkDialogButton
1216function PageLinkDialog($name, $desc = '', $url = '', $extraclass = '')
1217{
1218    //# as PageLink2, but add the option to ajax it in a popover window
1219    $link = PageLink2($name, $desc, $url);
1220    if ($link) {
1221        $link = str_replace('<a ', '<a class="button opendialog '.$extraclass.'" ', $link);
1222        $link .= '';
1223    }
1224
1225    return $link;
1226}
1227
1228function PageLinkDialogOnly($name, $desc = '', $url = '', $extraclass = '')
1229{
1230    //# as PageLink2, but add the option to ajax it in a popover window
1231    $link = PageLink2($name, $desc, $url);
1232    if ($link) {
1233        $link = str_replace('<a ', '<a class="opendialog '.$extraclass.'" ', $link);
1234        $link .= '';
1235    }
1236
1237    return $link;
1238}
1239
1240function PageLinkAjax($name, $desc = '', $url = '', $extraclass = '')
1241{
1242    //# as PageLink2, but add the option to ajax it in a popover window
1243    $link = PageLink2($name, $desc, $url);
1244    if ($link) {
1245        $link = str_replace('<a ', '<a class="ajaxable '.$extraclass.'" ', $link);
1246        $link .= '';
1247    }
1248
1249    return $link;
1250}
1251
1252function PageLinkClass($name, $desc = '', $url = '', $class = '', $title = '')
1253{
1254    $link = PageLink2($name, $desc, $url, false, $title);
1255    if (empty($class)) {
1256        $class = 'link';
1257    }
1258    if ($link) {
1259        $link = str_replace('<a ', '<a class="'.$class.'" ', $link);
1260        $link .= '';
1261    }
1262
1263    return $link;
1264}
1265
1266function PageLinkButton($name, $desc = '', $url = '', $extraclass = '', $title = '')
1267{
1268    return PageLinkClass($name, $desc, $url, 'button '.$extraclass, $title);
1269}
1270
1271function PageLinkActionButton($name, $desc = '', $url = '', $extraclass = '', $title = '')
1272{
1273    //# as PageLink2, but add the option to ajax it in a popover window
1274    $link = PageLink2($name, $desc, $url);
1275    if ($link) {
1276        $link = str_replace('<a ', '<a class="action-button '.$extraclass.'" ', $link);
1277        $link .= '';
1278    }
1279
1280    return $link;
1281}
1282
1283function SidebarLink($name, $desc, $url = '')
1284{
1285    if ($url) {
1286        $url = '&'.$url;
1287    }
1288    $access = accessLevel($name);
1289    if ($access == 'owner' || $access == 'all') {
1290        if ($name == 'processqueue' && !MANUALLY_PROCESS_QUEUE) {
1291            return '<!-- '.$desc.'-->';
1292        } elseif ($name == 'processbounces' && !MANUALLY_PROCESS_BOUNCES) {
1293            return '<!-- '.$desc.'-->';
1294        } else {
1295            return sprintf('<a href="./?page=%s%s" target="phplistwindow">%s</a>', $name, $url, mb_strtolower($desc));
1296        }
1297    } else {
1298        return "\n<!--$name disabled $access -->\n";
1299    }
1300//    return "\n$name disabled $access\n";
1301}
1302
1303function PageURL2($name, $desc = '', $url = '', $no_plugin = false)
1304{
1305    if (empty($name)) {
1306        return '';
1307    }
1308    if ($url) {
1309        $url = '&amp;'.$url;
1310    }
1311    $access = accessLevel($name);
1312    if ($access == 'owner' || $access == 'all' || $access == 'view') {
1313        if (!$no_plugin && !preg_match('/&amp;pi=/i',
1314                $name) && $_GET['pi'] && is_object($GLOBALS['plugins'][$_GET['pi']])
1315        ) {
1316            $pi = '&amp;pi='.$_GET['pi'];
1317        } else {
1318            $pi = '';
1319        }
1320
1321        return sprintf('./?page=%s%s%s%s', $name, $url, $pi, addCsrfGetToken());
1322    } else {
1323        return '';
1324    }
1325}
1326
1327function ListofLists($current, $fieldname, $subselect)
1328{
1329    //# @@TODO, this is slow on more than 150 lists. We should add caching or optimise
1330    $GLOBALS['systemTimer']->interval();
1331    $categoryhtml = array();
1332    //# add a hidden field, so that all checkboxes can be unchecked while keeping the field in POST to process it
1333    // $categoryhtml['unselect'] = '<input type="hidden" name="'.$fieldname.'[unselect]" value="1" />';
1334
1335    $categoryhtml['selected'] = '';
1336
1337    $categoryhtml['all'] = '<input type="hidden" name="' .$fieldname.'[unselect]" value="-1" />';
1338    if ($fieldname == 'targetlist') {
1339        $categoryhtml['all'] .= '
1340    <li><input type="checkbox" name="'.$fieldname.'[all]"';
1341        if (!empty($current['all'])) {
1342            $categoryhtml['all'] .= 'checked';
1343        }
1344        $categoryhtml['all'] .= ' />'.s('All Lists').'</li>';
1345
1346        $categoryhtml['all'] .= '<li><input type="checkbox" name="'.$fieldname.'[allactive]"';
1347        if (!empty($current['allactive'])) {
1348            $categoryhtml['all'] .= 'checked="checked"';
1349        }
1350        $categoryhtml['all'] .= ' />'.s('All Public Lists').'</li>';
1351    }
1352
1353    //# need a better way to suppress this
1354    if ($_GET['page'] != 'send') {
1355        $categoryhtml['all'] .= '<li>'.PageLinkDialog('addlist', s('Add a list')).'</li>';
1356    }
1357
1358    $result = Sql_query('select * from '.$GLOBALS['tables']['list'].$subselect.' order by category, name');
1359    $numLists = Sql_Affected_Rows();
1360
1361    while ($list = Sql_fetch_array($result)) {
1362        if (empty($list['category'])) {
1363            if ($numLists < 5) { //# for a small number of lists, add them to the @ tab
1364                $list['category'] = 'all';
1365            } else {
1366                $list['category'] = s('Uncategorised');
1367            }
1368        }
1369        if (!isset($categoryhtml[$list['category']])) {
1370            $categoryhtml[$list['category']] = '';
1371        }
1372        if (isset($current[$list['id']]) && $current[$list['id']]) {
1373            $list['category'] = 'selected';
1374        }
1375        $categoryhtml[$list['category']] .= sprintf('<li><input type="checkbox" name="'.$fieldname.'[%d]" value="%d" ',
1376            $list['id'], $list['id']);
1377        // check whether this message has been marked to send to a list (when editing)
1378        if (isset($current[$list['id']]) && $current[$list['id']]) {
1379            $categoryhtml[$list['category']] .= 'checked';
1380        }
1381        $categoryhtml[$list['category']] .= ' />'.htmlspecialchars(cleanListName(stripslashes($list['name'])));
1382        if ($list['active']) {
1383            $categoryhtml[$list['category']] .= ' <span class="activelist">'.s('Public list').'</span>';
1384        } else {
1385            $categoryhtml[$list['category']] .= ' <span class="inactivelist">'.s('Private list').'</span>';
1386        }
1387
1388        if (!empty($list['description'])) {
1389            $desc = nl2br(stripslashes(disableJavascript($list['description'])));
1390            $categoryhtml[$list['category']] .= "<br />$desc";
1391        }
1392        $categoryhtml[$list['category']] .= '</li>';
1393        $some = 1;
1394    }
1395    if (empty($categoryhtml['selected'])) {
1396        unset($categoryhtml['selected']);
1397    }
1398//  file_put_contents('/tmp/timer.log','ListOfLists '.$GLOBALS['systemTimer']->interval(). "\n",FILE_APPEND);
1399    return $categoryhtml;
1400}
1401
1402function listSelectHTML($current, $fieldname, $subselect, $alltab = '')
1403{
1404    $GLOBALS['systemTimer']->interval();
1405    $categoryhtml = ListofLists($current, $fieldname, $subselect);
1406
1407    $tabno = 1;
1408    $listindex = $listhtml = '';
1409    $some = count($categoryhtml);
1410
1411    if (!empty($alltab)) {
1412        //&& $some > 1) {
1413        //   unset($categoryhtml['all']);
1414        //## @@@TODO this has a weird effect when categories are numbers only eg years, because PHP renumbers them to 0,1,2
1415        //   array_unshift($categoryhtml,$alltab);
1416    }
1417
1418    if ($some > 0) {
1419        foreach ($categoryhtml as $category => $content) {
1420            if ($category == 'all') {
1421                $category = '@';
1422            }
1423/* "if" commented on 2017-5-8 to show tabs always fixing UI bugs on all themes with jQuery in checkboxes. I suggest to remove it permanently. */
1424//            if ($some > 1) { //# don't show tabs, when there's just one
1425                $listindex .= sprintf('<li><a href="#%s%d">%s</a></li>', $fieldname, $tabno, $category);
1426 //           }
1427            if ($fieldname == 'targetlist') {
1428                // Add select all checkbox in every category to select all lists in that category.
1429                if ($category == 'selected') {
1430                    $content = sprintf('<li class="selectallcategory"><input type="checkbox" name="all-lists-'.$fieldname.'-cat-'.str_replace(' ',
1431                                '-',
1432                                strtolower($category)).'" checked="checked">'.s('Select all').'</li>').$content;
1433                } elseif ($category != '@') {
1434                    $content = sprintf('<li class="selectallcategory"><input type="checkbox" name="all-lists-'.$fieldname.'-cat-'.str_replace(' ',
1435                                '-', strtolower($category)).'">'.s('Select all').'</li>').$content;
1436                }
1437            }
1438            $listhtml .= sprintf('<div class="%s" id="%s%d"><ul>%s</ul></div>',
1439                str_replace(' ', '-', strtolower($category)), $fieldname, $tabno, $content);
1440            ++$tabno;
1441        }
1442    }
1443
1444    $html = '<div class="tabbed"><ul>'.$listindex.'</ul>';
1445    $html .= $listhtml;
1446    $html .= '</div><!-- end of tabbed -->'; //# close tabbed
1447
1448    if (!$some) {
1449        $html = s('There are no lists available');
1450    }
1451// file_put_contents('/tmp/timer.log','ListSelectHTML '.$GLOBALS['systemTimer']->interval(). "\n",FILE_APPEND);
1452    return $html;
1453}
1454
1455function getSelectedLists($fieldname)
1456{
1457    $lists = array();
1458    if (!empty($_POST['addnewlist'])) {
1459        include 'editlist.php';
1460        $lists[$_SESSION['newlistid']] = $_SESSION['newlistid'];
1461
1462        return $lists;
1463    }
1464    if (!isset($_POST[$fieldname])) {
1465        return array();
1466    }
1467    if (!empty($_POST[$fieldname]['all'])) {
1468        //# load all lists
1469        $req = Sql_Query(sprintf('select id from %s', $GLOBALS['tables']['list']));
1470        while ($row = Sql_Fetch_Row($req)) {
1471            $lists[$row[0]] = $row[0];
1472        }
1473    } elseif (!empty($_POST[$fieldname]['allactive'])) {
1474        //# load all active lists
1475        $req = Sql_Query(sprintf('select id from %s where active', $GLOBALS['tables']['list']));
1476        while ($row = Sql_Fetch_Row($req)) {
1477            $lists[$row[0]] = $row[0];
1478        }
1479    } else {
1480        //# verify the lists are actually allowed
1481        $req = Sql_Query(sprintf('select id from %s', $GLOBALS['tables']['list']));
1482        while ($row = Sql_Fetch_Row($req)) {
1483            if (in_array($row[0], $_POST[$fieldname])) {
1484                $lists[$row[0]] = $row[0];
1485            }
1486        }
1487    }
1488
1489    return $lists;
1490}
1491
1492function hostName()
1493{
1494    if (HTTP_HOST) {
1495        return HTTP_HOST;
1496    } elseif (!empty($_SERVER['HTTP_HOST'])) {
1497        return $_SERVER['HTTP_HOST'];
1498    } else {
1499        //# could check SERVER_NAME as well
1500        return getConfig('website');
1501    }
1502}
1503
1504function Redirect($page)
1505{
1506    $website = hostName();
1507    header('Location: '.$GLOBALS['admin_scheme'].'://'.$website.$GLOBALS['adminpages']."/?page=$page");
1508    exit;
1509}
1510
1511function formatBytes($value)
1512{
1513    $gb = 1024 * 1024 * 1024;
1514    $mb = 1024 * 1024;
1515    $kb = 1024;
1516    $gbs = $value / $gb;
1517    if ($gbs > 1) {
1518        return sprintf('%2.2fGb', $gbs);
1519    }
1520    $mbs = $value / $mb;
1521    if ($mbs > 1) {
1522        return sprintf('%2.2fMb', $mbs);
1523    }
1524    $kbs = $value / $kb;
1525    if ($kbs > 1) {
1526        return sprintf('%dKb', $kbs);
1527    } else {
1528        return sprintf('%dBytes', $value);
1529    }
1530}
1531
1532function phpcfgsize2bytes($val)
1533{
1534    $val = trim($val);
1535    $last = mb_strtolower($val[strlen($val) - 1]);
1536    $result = substr($val, 0, -1);
1537    switch ($last) {
1538        case 'g':
1539            $result *= 1024;
1540        case 'm':
1541            $result *= 1024;
1542        case 'k':
1543            $result *= 1024;
1544    }
1545
1546    return $result;
1547}
1548
1549function Help($topic, $text = '?')
1550{
1551    return sprintf('<a href="help/?topic=%s" class="helpdialog" target="_blank">%s</a>', $topic, $text);
1552}
1553
1554/**
1555 * Checks if the list is private based on if the specified list id is active or not.
1556 *
1557 * @param int $listid
1558 * @return bool
1559 */
1560function isPrivateList($listid) {
1561
1562    $activeList = Sql_Fetch_Row_Query(sprintf('
1563          SELECT active
1564          FROM   %s
1565          WHERE  id = %d',
1566            $GLOBALS['tables']['list'], sql_escape($listid))
1567    );
1568
1569    return $activeList[0] == 0;
1570}
1571
1572// Debugging system, needs $debug = TRUE and $verbose = TRUE or $debug_log = {path} in config.php
1573// Hint: When using log make sure the file gets write permissions
1574
1575function dbg($variable, $description = 'Value', $nestingLevel = 0)
1576{
1577    //  smartDebug($variable, $description, $nestingLevel); //TODO Fix before release!
1578//  return;
1579
1580    global $config;
1581
1582    if (isset($config['debug']) && !$config['debug']) {
1583        return;
1584    }
1585
1586    if (is_array($variable)) {
1587        $tmp = $variable;
1588        $variable = '';
1589        foreach ($tmp as $key => $val) {
1590            $variable .= $key.'='.$val.';';
1591        }
1592    }
1593
1594    $msg = $description.': '.$variable;
1595
1596    if (isset($config['verbose']) && $config['verbose']) {
1597        echo "\n".'DBG: '.$msg.'<br/>'."\n";
1598    } elseif (isset($config['debug_log']) && $config['debug_log']) {
1599        $fp = @fopen($config['debug_log'], 'a');
1600        $line = '['.date('d M Y, H:i:s').'] '.$_SERVER['REQUEST_METHOD'].'-'.$_SERVER['REQUEST_URI'].'('.$GLOBALS['pagestats']['number_of_queries'].") $msg \n";
1601        @fwrite($fp, $line);
1602        @fclose($fp);
1603        //  $fp = fopen($config["sql_log"],"a");
1604        //  fwrite($fp,"$line");
1605        //  fclose($fp);
1606    }
1607}
1608
1609function PageData($id)
1610{
1611    global $tables;
1612    $req = Sql_Query(sprintf('select * from %s where id = %d', $tables['subscribepage_data'], $id));
1613    if (!Sql_Affected_Rows()) {
1614        $data = array();
1615        $data['header'] = getConfig('pageheader');
1616        $data['footer'] = getConfig('pagefooter');
1617        $data['button'] = 'Subscribe';
1618        $data['attributes'] = '';
1619        $req = Sql_Query(sprintf('select * from %s order by listorder', $GLOBALS['tables']['attribute']));
1620        while ($row = Sql_Fetch_Array($req)) {
1621            $data['attributes'] .= $row['id'].'+';
1622            $data[sprintf('attribute%03d', $row['id'])] = '';
1623            foreach (array(
1624                         'id',
1625                         'default_value',
1626                         'listorder',
1627                         'required',
1628                     ) as $key) {
1629                $data[sprintf('attribute%03d', $row['id'])] .= $row[$key].'###';
1630            }
1631        }
1632        $data['attributes'] = substr($data['attributes'], 0, -1);
1633        $data['htmlchoice'] = 'checkforhtml';
1634        $lists = array();
1635        $req = Sql_Query(sprintf('select * from %s where active order by listorder', $GLOBALS['tables']['list']));
1636        while ($row = Sql_Fetch_Array($req)) {
1637            array_push($lists, $row['id']);
1638        }
1639        $data['lists'] = implode(',', $lists);
1640        $data['intro'] = $GLOBALS['strSubscribeInfo'];
1641        $data['emaildoubleentry'] = 'yes';
1642        $data['thankyoupage'] = '';
1643        foreach ($data as $key => $val) {
1644            $data[$key] = str_ireplace('[organisation_name]', $GLOBALS['organisation_name'], $val);
1645        }
1646
1647        return $data;
1648    }
1649    while ($row = Sql_Fetch_Array($req)) {
1650        if (in_array($row['name'], array(
1651            'title',
1652            'language_file',
1653            'intro',
1654            'header',
1655            'footer',
1656            'thankyoupage',
1657            'button',
1658            'htmlchoice',
1659            'emaildoubleentry',
1660            'ajax_subscribeconfirmation',
1661        ))) {
1662            $data[$row['name']] = stripslashes($row['data']);
1663        } else {
1664            $data[$row['name']] = $row['data'];
1665        }
1666        $data[$row['name']] = preg_replace('/<\?=VERSION\?>/i', VERSION, $data[$row['name']]);
1667        $data[$row['name']] = str_ireplace('[organisation_name]', $GLOBALS['organisation_name'], $data[$row['name']]);
1668        $data[$row['name']] = str_ireplace('[website]', $GLOBALS['website'], $data[$row['name']]);
1669        $data[$row['name']] = str_ireplace('[website]', $GLOBALS['domain'], $data[$row['name']]);
1670        //@@ TODO, add call to plugins here?
1671    }
1672    if (!isset($data['lists'])) {
1673        $data['lists'] = '';
1674    }
1675    if (!isset($data['emaildoubleentry'])) {
1676        $data['emaildoubleentry'] = '';
1677    }
1678    if (!isset($data['rssdefault'])) {
1679        $data['rssdefault'] = '';
1680    }
1681    if (!isset($data['rssintro'])) {
1682        $data['rssintro'] = '';
1683    }
1684    if (!isset($data['rss'])) {
1685        $data['rss'] = '';
1686    }
1687    if (!isset($data['lists'])) {
1688        $data['lists'] = '';
1689    }
1690
1691    return $data;
1692}
1693
1694function PageAttributes($data)
1695{
1696    $attributes = explode('+', $data['attributes']);
1697    $attributedata = array();
1698    if (is_array($attributes)) {
1699        foreach ($attributes as $attribute) {
1700            if (isset($data[sprintf('attribute%03d', $attribute)])) {
1701                list($attributedata[$attribute]['id'], $attributedata[$attribute]['default_value'], $attributedata[$attribute]['listorder'], $attributedata[$attribute]['required']) = explode('###',
1702                    $data[sprintf('attribute%03d', $attribute)]);
1703                if (!isset($sorted) || !is_array($sorted)) {
1704                    $sorted = array();
1705                }
1706                $sorted[$attributedata[$attribute]['id']] = $attributedata[$attribute]['listorder'];
1707            }
1708        }
1709        if (isset($sorted) && is_array($sorted)) {
1710            $attributes = $sorted;
1711            asort($attributes);
1712        }
1713    }
1714
1715    return array(
1716        $attributes,
1717        $attributedata,
1718    );
1719}
1720
1721/**
1722 * Return either the short or long representation of a month in the language for the current admin.
1723 * Cache the set of month translations to avoid repeating the translations.
1724 *
1725 * @param string $month month number 01-12
1726 * @param bool   $short generate short or long representation of the month
1727 *
1728 * @return string
1729 */
1730function monthName($month, $short = 0)
1731{
1732    static $shortmonths;
1733    static $months;
1734
1735    if ($short) {
1736        if ($shortmonths === null) {
1737            $shortmonths = array(
1738                '',
1739                s('Jan'),
1740                s('Feb'),
1741                s('Mar'),
1742                s('Apr'),
1743                s('May'),
1744                s('Jun'),
1745                s('Jul'),
1746                s('Aug'),
1747                s('Sep'),
1748                s('Oct'),
1749                s('Nov'),
1750                s('Dec'),
1751            );
1752        }
1753
1754        return $shortmonths[(int) $month];
1755    }
1756
1757    if ($months === null) {
1758        $months = array(
1759            '',
1760            s('January'),
1761            s('February'),
1762            s('March'),
1763            s('April'),
1764            s('May'),
1765            s('June'),
1766            s('July'),
1767            s('August'),
1768            s('September'),
1769            s('October'),
1770            s('November'),
1771            s('December'),
1772        );
1773    }
1774
1775    return $months[(int) $month];
1776}
1777
1778/**
1779 * Format a date using a configurable format string.
1780 *      d   2-digit day with leading zero
1781 *      j   day without leading zero
1782 *      F   long representation of month
1783 *      m   2-digit month with leading zero
1784 *      n   month without leading zero
1785 *      M   short representation of month
1786 *      y   2-digit year
1787 *      Y   4-digit year
1788 * Optionally force a short representation to be used for the month.
1789 *
1790 * @param string $date  date as YYYY-MM-DD
1791 * @param bool   $short force short representation of the month
1792 *
1793 * @return string
1794 */
1795function formatDate($date, $short = 0)
1796{
1797    if ($date == '') {
1798        return '';
1799    }
1800    $format = getConfig('date_format');
1801    $year = substr($date, 0, 4);
1802    $month = substr($date, 5, 2);
1803    $day = substr($date, 8, 2);
1804    $specifiers = array(
1805        'Y' => $year,
1806        'y' => substr($year, 2, 2),
1807        'F' => monthName($month, $short),
1808        'M' => monthName($month, true),
1809        'm' => $month,
1810        'n' => +$month,
1811        'd' => $day,
1812        'j' => +$day,
1813    );
1814    $result = strtr($format, $specifiers);
1815
1816    return $result;
1817}
1818
1819function formatTime($time, $short = 0)
1820{
1821    return $time;
1822}
1823
1824function formatDateTime($datetime, $short = 0)
1825{
1826    if ($datetime == '') {
1827        return '';
1828    }
1829    $date = substr($datetime, 0, 10);
1830    $time = substr($datetime, 11, 8);
1831
1832    return formatDate($date, $short).' '.formatTime($time, $short);
1833}
1834
1835$oldestpoweredimage = 'iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAABUFvrSAAAABGdBTUEAALGPC/xhBQAAAMBQTFRFmQAAZgAAmgICmwUFnAgInQsLnxAQbw4OohYWcBERpBwcpiIiqCcnqiwsfCAgrDAwrjU1rzg4sTs7iTAws0FBtEVFtklJuU9Pu1VVn0pKkEREvltbtFxcwWRkw2trm1ZWrGNjx3V1y3x8zoWFqW5u0I6O15ycuoqK3aysxZqa3rm55s3N8t3d9+zs+fHx5t/f/Pf3/fr6////7+/vz8/PtbW1j4+Pb29vVVVVRkZGKioqExMTDg4OBwcHAwMDAAAAB4LGQwAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAHdElNRQfSBAITGhB/UY5ZAAAD2ElEQVR4nI2VC3uiOhCGoVqq9YbcZHGxIoI0SLGhIJdt8///1c4kHnVPhTpPK4TPvEzmpkTvsiK/73vckmAuSdJ93/26G5wEhsQN7uuaVTSrWP1BGT1WtCpgUWUf7FhVX1WWVZ/Hz/Qu6ltoSf8ZLFnxwfKypPBXZ02dsrQss7oovnJ+PZa0au6gHqJFT5KuwDmjGctZzp09lux4pF911RRFTT/x+geU8ifqe2T3pX8MEsM+ioY2BThHyyavm5TWRQbhKMS1KVJQOo24ivR/o/RY101Oi4Yd4SUVBoTmNaCqnOYV0POqKLtyR7zBNyoHVz+402nxZqI83uIi+KdSWjtOfFPYh+boeaB8D4N0Xx3LsnzjaRK5hqZOkNwK7u4rIsv6Nyrxl0t7YRmc3ApmneCdLK//efAWhxvPW63cpc3JreCU1QyrNj/31+tul5K1s+brtSzv0p3j7IS0ffHW+lT3kO3aljYbP7eBcyhk6BAKnXGJ6gv8y0NMmg4eD3G1pe97iIvs4OIpCjbearkw1PGoDQzFm7OU5U124sbI3G6HIriIcXY6pnAf+VzCF+kHCIhrm/NJK7iqM+gKdmmvV+Er8hPMHcY44bURrbn0HqGU+OAyxKIV3JQweWh9dphu8dgiCARzNwXujrsfvfCIkGiKUrBBsMvnpAl4xTThBm10qeO8uTQgBDE+XQkF1I4eyBr9fiM6SntC+DsjDqY+d9CTzAQcmHGCdwFX58xdOmKIlClHRQ7yee4gRoQ84VMOnp/BJFaUfcRvpZudF5/AcB2eYns6+z4QKxKgREOevDPYo6E7kjrAkDtw57B38PTgowOIULi65RIhXDpAVUC5ncGSBwF0O8C4W08xqk+pSOQ+XInc/bqWYlEUZ7BtSkpEO8DgzlTm9koPOn7G/i90MQn1a8kX/UFDKAMe48S2430b+BDjqVNsvCmBcPIERp6OuYuDaykCLrYH34a0WQTBmt0EH8hm6f7mhRu8QsCSEGYNFJHvuitYktW15AJX6x6bwt7JSlWNxRJO/ULf/E0QBjDAwGy05dJdeSfJ55INXJhAg9ZfEGHEfVaexzPNssWpcSyCTwvLsngvWQt76QqJzzUcmXPO7QLHq4H00FcGo8ncsHjFRq4Y5NocTFXVuWYAWkh8EoO76onbbwHHHh+oCAaX54aubxPqA9U0tNlsMpmMwSYzVTNMIeErTXCXx/fxsd+7Cd6MTzcPvcfBYIRkKwxD2KnB1vFo9CxsNJ6A2yZItmWdNOT2+73b4LMBGFzG/RrYXBU7uSkKfKA0UyEwVyJwe72Hh1u4v1tVRVPPqSx/AAAAAElFTkSuQmCC';
1836$olderpoweredimage = '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';
1837$oldpoweredimage = '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';
1838$newpoweredimage = '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';
1839
1840function FileNotFound($msg = '')
1841{
1842    ob_end_clean();
1843    header('HTTP/1.0 404 File Not Found', true, 404);
1844    if (defined('ERROR404PAGE') && is_file($_SERVER['DOCUMENT_ROOT'].'/'.ERROR404PAGE)) {
1845        echo file_get_contents($_SERVER['DOCUMENT_ROOT'].'/'.ERROR404PAGE);
1846        exit;
1847    }
1848
1849    printf('<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1>The requested document was not found on this server<br/>%s<br/>Please contact the <a href="mailto:%s?subject=File not Found: %s">Administrator</a><p><hr><address><a href="http://phplist.com" target="_phplist">phpList</a> version %s</address></body></html>',
1850        $msg, getConfig('admin_address'),
1851        strip_tags($_SERVER['REQUEST_URI']), VERSION);
1852    exit;
1853}
1854
1855function findMime($filename)
1856{
1857    list($name, $ext) = explode('.', $filename);
1858    if (!$ext || !is_file(MIMETYPES_FILE)) {
1859        return DEFAULT_MIMETYPE;
1860    }
1861    $fp = @fopen(MIMETYPES_FILE, 'r');
1862    $contents = fread($fp, filesize(MIMETYPES_FILE));
1863    fclose($fp);
1864    $lines = explode("\n", $contents);
1865    foreach ($lines as $line) {
1866        if (!preg_match("/^\s*#/", $line) && !preg_match("/^\s*$/", $line)) {
1867            $line = preg_replace("/\t/", ' ', $line);
1868            $items = explode(' ', $line);
1869            $mime = array_shift($items);
1870            foreach ($items as $extension) {
1871                $extension = trim($extension);
1872                if ($ext == $extension) {
1873                    return $mime;
1874                }
1875            }
1876        }
1877    }
1878
1879    return DEFAULT_MIMETYPE;
1880}
1881
1882function excludedDateForRepetition($date)
1883{
1884    if (!is_array($GLOBALS['repeat_exclude'])) {
1885        return 0;
1886    }
1887    foreach ($GLOBALS['repeat_exclude'] as $exclusion) {
1888        $formatted_value = Sql_Fetch_Row_Query(sprintf('select date_format("%s","%s")', $date, $exclusion['format']));
1889        foreach ($exclusion['values'] as $disallowed) {
1890            if ($formatted_value[0] == $disallowed) {
1891                return 1;
1892            }
1893        }
1894    }
1895
1896    return 0;
1897}
1898
1899function delimited($data)
1900{
1901    $delimitedData = '';
1902    reset($data);
1903    foreach ($data as $key => $val) {
1904        $delimitedData .= $key.'KEYVALSEP'.$val.'ITEMSEP';
1905    }
1906    $length = strlen($delimitedData);
1907
1908    return substr($delimitedData, 0, -7);
1909}
1910
1911function parseDelimitedData($value)
1912{
1913    $data = array();
1914    $rawdata = explode('ITEMSEP', $value);
1915    foreach ($rawdata as $item) {
1916        list($key, $val) = explode('KEYVALSEP', $item);
1917        $data[$key] = ltrim($val);
1918    }
1919
1920    return $data;
1921}
1922
1923function repeatMessage($msgid)
1924{
1925    //  if (!USE_REPETITION && !USE_rss) return;
1926
1927    $data = loadMessageData($msgid);
1928    //# do not repeat when it has already been done
1929    if ($data['repeatinterval'] == 0 || !empty($data['repeatedid'])) {
1930        return;
1931    }
1932
1933    // calculate the future embargo, a multiple of repeatinterval minutes after the current embargo
1934
1935    $msgdata = Sql_Fetch_Array_Query(
1936        sprintf(
1937            'SELECT *,
1938        embargo +
1939            INTERVAL (FLOOR(TIMESTAMPDIFF(MINUTE, embargo, GREATEST(embargo, NOW())) / repeatinterval) + 1) * repeatinterval MINUTE AS newembargo
1940        FROM %s
1941        WHERE id = %d AND now() < repeatuntil',
1942            $GLOBALS['tables']['message'],
1943            $msgid
1944        )
1945    );
1946
1947    if (!$msgdata) {
1948        logEvent("Message $msgid not repeated due to reaching the repeatuntil date");
1949
1950        return;
1951    }
1952
1953    // check whether the new embargo is not on an exclusion
1954    if (isset($GLOBALS['repeat_exclude']) && is_array($GLOBALS['repeat_exclude'])) {
1955        $loopcnt = 0;
1956
1957        while (excludedDateForRepetition($msgdata['newembargo'])) {
1958            if (++$loopcnt > 15) {
1959                logEvent("Unable to find new embargo date too many exclusions? for message $msgid");
1960
1961                return;
1962            }
1963            $result = Sql_Fetch_Array_Query(
1964                sprintf(
1965                    "SELECT '%s' + INTERVAL repeatinterval MINUTE AS newembargo
1966            FROM %s
1967            WHERE id = %d",
1968                    $msgdata['newembargo'],
1969                    $GLOBALS['tables']['message'],
1970                    $msgid
1971                )
1972            );
1973            $msgdata['newembargo'] = $result['newembargo'];
1974        }
1975    }
1976
1977    // copy the new message
1978    Sql_Query(sprintf('
1979    insert into %s (entered) values(now())', $GLOBALS['tables']['message']));
1980    $newid = Sql_Insert_id();
1981    require dirname(__FILE__).'/structure.php';
1982    if (!is_array($DBstruct['message'])) {
1983        logEvent("Error including structure when trying to duplicate message $msgid");
1984
1985        return;
1986    }
1987
1988    //  Do not copy columns that use default values or are explicitly set, or indices
1989    $columnsToCopy = array_diff(
1990        array_keys($DBstruct['message']),
1991        array(
1992            'id', 'entered', 'modified', 'embargo', 'status', 'sent', 'processed', 'astext', 'ashtml',
1993            'astextandhtml', 'aspdf', 'astextandpdf', 'viewed', 'bouncecount', 'sendstart', 'uuid',
1994        )
1995    );
1996    $columnsToCopy = preg_grep('/^index_/', $columnsToCopy, PREG_GREP_INVERT);
1997
1998    foreach ($columnsToCopy as $column) {
1999        Sql_Query(sprintf('update %s set %s = "%s" where id = %d',
2000            $GLOBALS['tables']['message'], $column, addslashes($msgdata[$column]), $newid));
2001    }
2002    Sql_Query(sprintf(
2003        'update %s set embargo = "%s",status = "submitted", uuid="%s" where id = %d',
2004        $GLOBALS['tables']['message'],
2005        $msgdata['newembargo'],
2006        (string) UUID::generate(4),
2007        $newid
2008    ));
2009
2010    // copy rows in messagedata except those that are explicitly set
2011    $req = Sql_Query(sprintf(
2012        "SELECT *
2013    FROM %s
2014    WHERE id = %d AND name NOT IN ('id', 'embargo', 'finishsending')",
2015        $GLOBALS['tables']['messagedata'], $msgid
2016    ));
2017    while ($row = Sql_Fetch_Array($req)) {
2018        setMessageData($newid, $row['name'], $row['data']);
2019    }
2020
2021    list($e['year'], $e['month'], $e['day'], $e['hour'], $e['minute'], $e['second']) =
2022        sscanf($msgdata['newembargo'], '%04d-%02d-%02d %02d:%02d:%02d');
2023    unset($e['second']);
2024    setMessageData($newid, 'embargo', $e);
2025
2026    $finishSending = time() + DEFAULT_MESSAGEAGE;
2027    $finish = array(
2028        'year'   => date('Y', $finishSending),
2029        'month'  => date('m', $finishSending),
2030        'day'    => date('d', $finishSending),
2031        'hour'   => date('H', $finishSending),
2032        'minute' => date('i', $finishSending),
2033    );
2034    setMessageData($newid, 'finishsending', $finish);
2035
2036    // lists
2037    $req = Sql_Query(sprintf('select listid from %s where messageid = %d', $GLOBALS['tables']['listmessage'], $msgid));
2038    while ($row = Sql_Fetch_Row($req)) {
2039        Sql_Query(sprintf('insert into %s (messageid,listid,entered) values(%d,%d,now())',
2040            $GLOBALS['tables']['listmessage'], $newid, $row[0]));
2041    }
2042
2043    // attachments
2044    $req = Sql_Query(sprintf('select * from %s,%s where %s.messageid = %d and %s.attachmentid = %s.id',
2045        $GLOBALS['tables']['message_attachment'], $GLOBALS['tables']['attachment'],
2046        $GLOBALS['tables']['message_attachment'], $msgid, $GLOBALS['tables']['message_attachment'],
2047        $GLOBALS['tables']['attachment']));
2048    while ($row = Sql_Fetch_Array($req)) {
2049        if (is_file($row['remotefile'])) {
2050            // if the "remote file" is actually local, we want to refresh the attachment, so we set
2051            // filename to nothing
2052            $row['filename'] = '';
2053        }
2054
2055        Sql_Query(sprintf('insert into %s (filename,remotefile,mimetype,description,size)
2056      values("%s","%s","%s","%s",%d)',
2057            $GLOBALS['tables']['attachment'], addslashes($row['filename']), addslashes($row['remotefile']),
2058            addslashes($row['mimetype']), addslashes($row['description']), $row['size']));
2059        $attid = Sql_Insert_id();
2060        Sql_Query(sprintf('insert into %s (messageid,attachmentid) values(%d,%d)',
2061            $GLOBALS['tables']['message_attachment'], $newid, $attid));
2062    }
2063    logEvent("Message $msgid was successfully rescheduled as message $newid");
2064    //# remember we duplicated, in order to avoid doing it again (eg when requeuing)
2065    setMessageData($msgid, 'repeatedid', $newid);
2066    if (getConfig('pqchoice') == 'phplistdotcom') {
2067        activateRemoteQueue();
2068    }
2069}
2070
2071function versionCompare($thisversion, $latestversion)
2072{
2073    // return 1 if $thisversion is larger or equal to $latestversion
2074
2075    list($major1, $minor1, $sub1) = sscanf($thisversion, '%d.%d.%d');
2076    list($major2, $minor2, $sub2) = sscanf($latestversion, '%d.%d.%d');
2077    if ($major1 > $major2) {
2078        return 1;
2079    }
2080    if ($major1 == $major2 && $minor1 > $minor2) {
2081        return 1;
2082    }
2083    if ($major1 == $major2 && $minor1 == $minor2 && $sub1 >= $sub2) {
2084        return 1;
2085    }
2086
2087    return 0;
2088}
2089
2090function cleanArray($array)
2091{
2092    $result = array();
2093    if (!is_array($array)) {
2094        return $array;
2095    }
2096    foreach ($array as $key => $val) {
2097        //# 0 is a valid key
2098        if (isset($key) && !empty($val)) {
2099            $result[$key] = $val;
2100        }
2101    }
2102
2103    return $result;
2104}
2105
2106function cl_processtitle($title)
2107{
2108    $title = preg_replace('/[^\w-]/', '', $title);
2109    if (function_exists('cli_set_process_title')) { // PHP5.5 and up
2110        cli_set_process_title('phpList:'.$GLOBALS['installation_name'].':'.$title);
2111    } elseif (function_exists('setproctitle')) { // pecl extension
2112        setproctitle('phpList:'.$GLOBALS['installation_name'].':'.$title);
2113    }
2114}
2115
2116function cl_output($message)
2117{
2118    if (!empty($GLOBALS['commandline'])) {
2119        @ob_end_clean();
2120        echo $GLOBALS['installation_name'].' - '.strip_tags($message)."\n";
2121        @ob_start();
2122    }
2123}
2124
2125function cl_progress($message)
2126{
2127    if ($GLOBALS['commandline']) {
2128        @ob_end_clean();
2129        echo $GLOBALS['installation_name'].' - '.strip_tags($message)."\r";
2130        @ob_start();
2131    }
2132}
2133
2134function phplist_shutdown()
2135{
2136    //  output( "Script status: ".connection_status(),0); # with PHP 4.2.1 buggy. http://bugs.php.net/bug.php?id=17774
2137    $status = connection_status();
2138    if ($GLOBALS['mail_error_count']) {
2139        $message = "Some errors occurred in the phpList Mailinglist System\n"
2140            .'URL: '.$GLOBALS['admin_scheme'].'://'.hostName()."{$_SERVER['REQUEST_URI']}\n"
2141            ."Error message(s):\n\n"
2142
2143            .$GLOBALS['mail_error'];
2144        $message .= "\n==== debugging information\n\nSERVER Vars\n";
2145        if (is_array($_SERVER)) {
2146            foreach ($_SERVER as $key => $val) {
2147                if (stripos($key, 'password') === false) {
2148                    $message .= $key.'='.serialize($val)."\n";
2149                }
2150            }
2151        }
2152        foreach ($GLOBALS['plugins'] as $pluginname => $plugin) {
2153            $plugin->processError($message);
2154        }
2155//   sendMail(getConfig("report_address"),$GLOBALS["installation_name"]." Mail list error",$message);
2156    }
2157
2158//  print "Phplist shutdown $status";
2159//  exit;
2160}
2161
2162function trimArray($array)
2163{
2164    $result = array();
2165    if (!is_array($array)) {
2166        return $array;
2167    }
2168    foreach ($array as $key => $val) {
2169        $testval = trim($val);
2170        if (isset($key) && !empty($testval)) {
2171            $result[$key] = $val;
2172        }
2173    }
2174
2175    return $result;
2176}
2177
2178register_shutdown_function('phplist_shutdown');
2179
2180function secs2time($secs)
2181{
2182    $years = $days = $hours = $mins = 0;
2183    $hours = (int) ($secs / 3600);
2184    $secs = $secs - ($hours * 3600);
2185    if ($hours > 24) {
2186        $days = (int) ($hours / 24);
2187        $hours = $hours - (24 * $days);
2188    }
2189    if ($days > 365) { //# a well, an estimate
2190        $years = (int) ($days / 365);
2191        $days = $days - ($years * 365);
2192    }
2193    $mins = (int) ($secs / 60);
2194    $secs = (int) ($secs % 60);
2195
2196    $format = compact('years', 'days', 'hours', 'mins');
2197
2198    $output = '';
2199
2200    foreach($format as $unit => $value) {
2201        if ($value > 0) {
2202              $output .= ' '.$value.' '.s($unit);
2203        }
2204    }
2205
2206    if ($secs) {
2207        $output .= ' '.sprintf('%02d', $secs).' '.s('secs');
2208    }
2209
2210    return $output;
2211}
2212
2213function listPlaceHolders()
2214{
2215    $html = '<table border="1"><tr><td><strong>'.s('Attribute').'</strong></td><td><strong>'.s('Placeholder').'</strong></td></tr>';
2216    $req = Sql_query('
2217    select
2218        name
2219    from
2220        '.$GLOBALS['tables']['attribute'].'
2221    order by
2222        listorder
2223    ');
2224    while ($row = Sql_Fetch_Row($req)) {
2225        if (strlen($row[0]) <= 30) {
2226            $html .= sprintf('<tr><td>%s</td><td>[%s]</td></tr>', $row[0], strtoupper(cleanAttributeName($row[0])));
2227        }
2228    }
2229    $html .= '</table>';
2230
2231    return $html;
2232}
2233
2234//# clean out chars that make preg choke
2235//# primarily used for parsing the placeholders in emails.
2236function cleanAttributeName($name)
2237{
2238    $name = str_replace('(', '', $name);
2239    $name = str_replace(')', '', $name);
2240    $name = str_replace('/', '', $name);
2241    $name = str_replace('\\', '', $name);
2242    $name = str_replace('*', '', $name);
2243    $name = str_replace('.', '', $name);
2244
2245    return $name;
2246}
2247
2248function cleanCommaList($sList)
2249{
2250    if (strpos($sList, ',') === false) {
2251        return $sList;
2252    }
2253    $aList = explode(',', $sList);
2254
2255    return implode(',', trimArray($aList));
2256}
2257
2258function printobject($object)
2259{
2260    if (!is_object($object)) {
2261        echo 'Not an object';
2262
2263        return;
2264    }
2265    $class = get_class($object);
2266    echo "Class: $class<br/>";
2267    $vars = get_object_vars($object);
2268    echo 'Vars:';
2269    printArray($vars);
2270}
2271
2272function printarray($array)
2273{
2274    if (is_object($array)) {
2275        return printObject($array);
2276    }
2277    if (!is_array($array)) {
2278        return;
2279    }
2280    foreach ($array as $key => $value) {
2281        if (is_array($value)) {
2282            echo $key.'(array):<blockquote>';
2283            printarray($value); //recursief!!
2284            echo '</blockquote>';
2285        } elseif (is_object($value)) {
2286            echo $key.'(object):<blockquote>';
2287            printobject($value);
2288            echo '</blockquote>';
2289        } else {
2290            echo $key.'==>'.$value.'<br />';
2291        }
2292    }
2293}
2294
2295function simplePaging($baseurl, $start, $total, $numpp, $itemname = '')
2296{
2297    $start = max(0, $start);
2298    $end = min($total, $start + $numpp);
2299
2300    if (!empty($itemname)) {
2301        $text = $GLOBALS['I18N']->get('Listing %d to %d of %d');
2302    } else {
2303        $text = $GLOBALS['I18N']->get('Listing %d to %d');
2304    }
2305    $listing = sprintf($text, $start + 1, $end, $total).' '.$itemname;
2306
2307    if ($total < $numpp) {
2308        return $listing;
2309    }
2310    // The last page displays the remaining items
2311    $remainingItems = $total % $numpp;
2312
2313    if ($remainingItems == 0) {
2314        $remainingItems = $numpp;
2315    }
2316    $startLast = $total - $remainingItems;
2317
2318    return '<div class="paging">
2319    <p class="range">' .$listing.'</p><div class="controls">
2320    <a title="' .$GLOBALS['I18N']->get('First Page').'" class="first" href="'.PageUrl2($baseurl.'&amp;start=0').'"></a>
2321    <a title="' .$GLOBALS['I18N']->get('Previous').'" class="previous" href="'.PageUrl2($baseurl.sprintf('&amp;start=%d',
2322            max(0, $start - $numpp))).'"></a>
2323    <a title="' .$GLOBALS['I18N']->get('Next').'" class="next" href="'.PageUrl2($baseurl.sprintf('&amp;start=%d',
2324            min($startLast, $start + $numpp))).'"></a>
2325    <a title="' .$GLOBALS['I18N']->get('Last Page').'" class="last" href="'.PageUrl2($baseurl.sprintf('&amp;start=%d',
2326            $startLast)).'"></a>
2327    </div></div>
2328  ';
2329}
2330
2331function Paging($base_url, $start, $total, $numpp = 10, $label = '')
2332{
2333    $page = 1;
2334    $window = 8; //# size left and right of current
2335    $data = ''; //PagingPrevious($base_url,$start,$total,$numpp,$label);#.'&nbsp;|&nbsp;';
2336    if (!isset($GLOBALS['config']['paginglabeltitle'])) {
2337        $labeltitle = $label;
2338    } else {
2339        $labeltitle = $GLOBALS['config']['paginglabeltitle'];
2340    }
2341    if ($total < $numpp) {
2342        return '';
2343    }
2344
2345    for ($i = 0; $i <= $total; $i += $numpp) {
2346        if ($i == $start) {
2347            $data .= sprintf('<a class="current paging-item" title="%s %s" class="paging-item">%s%s</a>', $labeltitle,
2348                $page, $label, $page);
2349        } //# only show 5 left and right of current
2350        elseif ($i > $start - $window * $numpp && $i < $start + $window * $numpp) {
2351            //    else
2352            $data .= sprintf('<a href="%s&amp;s=%d" title="%s %s" rel="nofollow" class="paging-item">%s%s</a>',
2353                $base_url, $i, $labeltitle, $page, $label, $page);
2354        }
2355        ++$page;
2356    }
2357    if ($page == 1) {
2358        return '';
2359    }
2360    // $data .= PagingNext($base_url,$start,$total,$numpp,$label,$page);
2361    return '<div class="paging">'.PagingPrevious($base_url, $start, $total, $numpp,
2362        $label).'<div class="items">'.$data.'</div>'.PagingNext($base_url, $start, $total, $numpp,
2363        $label).'</div>';
2364
2365    return '<div class="paging"><a class="prev browse left">&lt;&lt;</a><div class="items">'.$data.'</div><a class="next browse right">&gt;&gt;</a></div>';
2366}
2367
2368function PagingNext($base_url, $start, $total, $numpp, $label = '')
2369{
2370    if (!isset($GLOBALS['config']['pagingnext'])) {
2371        $GLOBALS['config']['pagingnext'] = '&gt;&gt;';
2372    }
2373    if (($start + $numpp - 1) < $total) {
2374        $data = sprintf('<a href="%s&amp;s=%d" title="Next" class="pagingnext paging-item" rel="nofollow">%s</a>',
2375            $base_url, $start + $numpp, $GLOBALS['config']['pagingnext']);
2376    } else {
2377        $data = sprintf('<a class="pagingnext paging-item">%s</a>', $GLOBALS['config']['pagingnext']);
2378    }
2379
2380    return $data;
2381}
2382
2383function PagingPrevious($base_url, $start, $total, $numpp, $label = '')
2384{
2385    if (!isset($GLOBALS['config']['pagingback'])) {
2386        $GLOBALS['config']['pagingback'] = '&lt;&lt;';
2387    }
2388    $page = 1;
2389    if ($start > 1) {
2390        $data = sprintf('<a href="%s&amp;s=%d" title="Previous" class="pagingprevious paging-item" rel="nofollow">%s</a>',
2391            $base_url, $start - $numpp, $GLOBALS['config']['pagingback']);
2392    } else {
2393        $data = sprintf('<a class="pagingprevious paging-item">%s</a>', $GLOBALS['config']['pagingback']);
2394    }
2395
2396    return $data;
2397}
2398
2399class timer
2400{
2401    public $start;
2402    public $previous = 0;
2403
2404    public function __construct()
2405    {
2406        $now = gettimeofday();
2407        $this->start = $now['sec'] * 1000000 + $now['usec'];
2408    }
2409
2410    public function elapsed($seconds = 0)
2411    {
2412        $now = gettimeofday();
2413        $end = $now['sec'] * 1000000 + $now['usec'];
2414        $elapsed = $end - $this->start;
2415        if ($seconds) {
2416            return sprintf('%0.10f', $elapsed / 1000000);
2417        } else {
2418            return sprintf('%0.10f', $elapsed);
2419        }
2420    }
2421
2422    public function interval($seconds = 0)
2423    {
2424        $now = gettimeofday();
2425        $end = $now['sec'] * 1000000 + $now['usec'];
2426        if (!$this->previous) {
2427            $elapsed = $end - $this->start;
2428        } else {
2429            $elapsed = $end - $this->previous;
2430        }
2431        $this->previous = $end;
2432
2433        if ($seconds) {
2434            return sprintf('%0.10f', $elapsed / 1000000);
2435        } else {
2436            return sprintf('%0.10f', $elapsed);
2437        }
2438    }
2439}
2440