diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 8184c9a24..e6ce1f5f8 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: ['7.4'] + php-version: ['8.2'] experimental: [false] steps: @@ -93,7 +93,7 @@ jobs: sudo php -S 0.0.0.0:80 -t public_html > /dev/null 2>&1 & - name: Check PHP syntax errors - uses: overtrue/phplint@2.4.1 + uses: overtrue/phplint@9.1.2 with: path: ./public_html options: --exclude=base/vendor diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ce4c002df..4f381b35a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,14 +11,10 @@ jobs: strategy: fail-fast: false matrix: - php-version: ['7.4', '8.0', '8.1'] + php-version: ['7.4', '8.0', '8.1','8.2'] experimental: [false] include: - - php-version: 7.2 - experimental: true - - php-version: 7.3 - experimental: true - - php-version: 8.2 + - php-version: 8.3 experimental: true steps: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 07e7fbb4a..3362d2d27 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ Please follow the guidelines below when creating an issue so that your issue can **Avoid duplicated issues** -Before you report an issue, please search through [existing issues on Mantis](https://mantis.phplist.com) and [GitHub](https://github.com/phpList/phplist3/issues) to see if your issue is already reported or fixed to make sure you are not reporting a duplicated issue. +Before you report an issue, please search through [existing issues](https://github.com/phpList/phplist3/issues) to see if your issue is already reported or fixed to make sure you are not reporting a duplicated issue. Also, make sure you have the latest version of phpList and see if the issue still exists. diff --git a/Dockerfile.release b/Dockerfile.release index 132029dca..dd4522da2 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -1,5 +1,5 @@ -FROM debian:buster-slim +FROM debian:bookworm-slim LABEL maintainer="michiel@phplist.com" diff --git a/VERSION b/VERSION index 044941ac8..8c177dddd 100755 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ # file that keeps track of the latest tag in cvs and the corresponding version # this automates publishing a new version, when it's tagged # if you don't understand this, don't worry. You don't need this file -VERSION=3.6.13 +VERSION=3.6.15 diff --git a/composer.lock b/composer.lock index ae13e7560..ecc24f6d8 100644 --- a/composer.lock +++ b/composer.lock @@ -80,12 +80,12 @@ "source": { "type": "git", "url": "https://github.com/bramley/phplist-plugin-common.git", - "reference": "4c09fe0dc33d5e0dfcda57f22aa722f399e070b9" + "reference": "6a9bf17c068f43be906e482010760cbce61a1766" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bramley/phplist-plugin-common/zipball/4c09fe0dc33d5e0dfcda57f22aa722f399e070b9", - "reference": "4c09fe0dc33d5e0dfcda57f22aa722f399e070b9", + "url": "https://api.github.com/repos/bramley/phplist-plugin-common/zipball/6a9bf17c068f43be906e482010760cbce61a1766", + "reference": "6a9bf17c068f43be906e482010760cbce61a1766", "shasum": "" }, "require": { @@ -115,10 +115,13 @@ "plugins/CommonPlugin/vendor/ext/", "plugins/CommonPlugin/vendor/jmathai/php-multi-curl/src", "plugins/CommonPlugin/vendor/katzgrau/klogger/src", + "plugins/CommonPlugin/vendor/kodus/db-cache/src", + "plugins/CommonPlugin/vendor/kodus/file-cache/src", "plugins/CommonPlugin/vendor/mouf/picotainer/src", "plugins/CommonPlugin/vendor/pelago/emogrifier/src", "plugins/CommonPlugin/vendor/psr/container/src", "plugins/CommonPlugin/vendor/psr/log/Psr", + "plugins/CommonPlugin/vendor/psr/simple-cache/src", "plugins/CommonPlugin/vendor/sabberworm/php-css-parser/src", "plugins/CommonPlugin/vendor/symfony/css-selector", "plugins/CommonPlugin/vendor/symfony/filesystem" @@ -140,10 +143,10 @@ "phplist" ], "support": { - "source": "https://github.com/bramley/phplist-plugin-common/tree/3.29.1", + "source": "https://github.com/bramley/phplist-plugin-common/tree/3.33.0", "issues": "https://github.com/bramley/phplist-plugin-common/issues" }, - "time": "2023-09-06T16:55:37+00:00" + "time": "2024-02-15T11:36:33+00:00" }, { "name": "bramley/phplist-plugin-segment", @@ -151,12 +154,12 @@ "source": { "type": "git", "url": "https://github.com/bramley/phplist-plugin-segment.git", - "reference": "3c99756eb4c85a124a07172ea77eae00caf6499b" + "reference": "50850afce743b574493f9d13ee219868ae1ec417" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bramley/phplist-plugin-segment/zipball/3c99756eb4c85a124a07172ea77eae00caf6499b", - "reference": "3c99756eb4c85a124a07172ea77eae00caf6499b", + "url": "https://api.github.com/repos/bramley/phplist-plugin-segment/zipball/50850afce743b574493f9d13ee219868ae1ec417", + "reference": "50850afce743b574493f9d13ee219868ae1ec417", "shasum": "" }, "default-branch": true, @@ -174,10 +177,10 @@ "phplist" ], "support": { - "source": "https://github.com/bramley/phplist-plugin-segment/tree/2.13.2", + "source": "https://github.com/bramley/phplist-plugin-segment/tree/2.14.1", "issues": "https://github.com/bramley/phplist-plugin-segment/issues" }, - "time": "2023-04-03T14:57:27+00:00" + "time": "2023-12-12T10:10:51+00:00" }, { "name": "bramley/phplist-plugin-updater", diff --git a/public_html/lists/admin/CsvReader.php b/public_html/lists/admin/CsvReader.php index d5d3cb68b..fe5e15fca 100644 --- a/public_html/lists/admin/CsvReader.php +++ b/public_html/lists/admin/CsvReader.php @@ -16,7 +16,6 @@ class CsvReader */ public function __construct($filename, $delimiter) { - ini_set('auto_detect_line_endings', true); $this->fh = fopen($filename, 'r'); $this->delimiter = $delimiter; $this->totalRows = 0; diff --git a/public_html/lists/admin/actions/import1.php b/public_html/lists/admin/actions/import1.php index 9e0a69fd9..e73631edc 100644 --- a/public_html/lists/admin/actions/import1.php +++ b/public_html/lists/admin/actions/import1.php @@ -53,12 +53,13 @@ if (!is_email($email) && $omit_invalid) { unset($email, $info); $count_invalid_emails++; + } else { + //# actually looks like the "info" bit will get lost, but + //# in a way, that doesn't matter + $user_list[$email] = array( + 'info' => $info, + ); } - //# actually looks like the "info" bit will get lost, but - //# in a way, that doesn't matter - $user_list[$email] = array( - 'info' => $info, - ); } $count_email_add = 0; diff --git a/public_html/lists/admin/actions/listmembercount.php b/public_html/lists/admin/actions/listmembercount.php index edf7cb6de..e0b2b0127 100644 --- a/public_html/lists/admin/actions/listmembercount.php +++ b/public_html/lists/admin/actions/listmembercount.php @@ -49,11 +49,11 @@ function listMemberCounts($listId) .'%s, '.' ' .'%s'.')', s('Confirmed and not blacklisted members'), - number_format($counts['confirmed']), + number_format(!empty($counts['confirmed']) ? $counts['confirmed'] : 0), s('Unconfirmed and not blacklisted members'), - number_format($counts['notconfirmed']), + number_format(!empty($counts['notconfirmed']) ? $counts['notconfirmed'] : 0), s('Blacklisted members'), - number_format($counts['blacklisted']) + number_format(!empty($counts['blacklisted']) ? $counts['blacklisted'] : 0) ); return $membersDisplay; diff --git a/public_html/lists/admin/admin.php b/public_html/lists/admin/admin.php index 557cef1ba..f505ee747 100644 --- a/public_html/lists/admin/admin.php +++ b/public_html/lists/admin/admin.php @@ -18,6 +18,7 @@ echo Error(s('No Access')); return; } +$accesslevel = 'all'; if (!empty($_POST['change'])) { if (!verifyToken()) { //# csrf check, should be added in more places @@ -102,7 +103,7 @@ 'statistics' => !empty($_POST['statistics']), 'settings' => !empty($_POST['settings']), ); - Sql_Query(sprintf('update %s set modified=now(), modifiedby = "%s", privileges = "%s" where id = %d', + Sql_Query(sprintf('update %s set modifiedby = "%s", privileges = "%s" where id = %d', $GLOBALS['tables']['admin'], adminName($_SESSION['logindetails']['id']), sql_escape(serialize($privs)), $id)); @@ -157,7 +158,7 @@ if (isset($data['privileges'])) { $privileges = unserialize($data['privileges']); } else { - $privileges = array(); + $privileges = array('subscribers' => 0, 'campaigns' => 0, 'statistics' => 0, 'settings' => 0); } reset($struct); diff --git a/public_html/lists/admin/bounce.php b/public_html/lists/admin/bounce.php index cba6a682e..26155c9e9 100644 --- a/public_html/lists/admin/bounce.php +++ b/public_html/lists/admin/bounce.php @@ -18,6 +18,7 @@ $deletebounce = isset($_GET['deletebounce']); //BUGFIX #15286 - nickyoung $amount = isset($_GET['amount']) ? sprintf('%d', $_GET['amount']) : ''; //BUGFIX #15286 - CS2 $unconfirm = isset($_GET['unconfirm']); //BUGFIX #15286 - CS2 +$confirm = isset($_GET['confirm']); $maketext = isset($_GET['maketext']); //BUGFIX #15286 - CS2 $deleteuser = isset($_GET['deleteuser']); //BUGFIX #15286 - CS2 @@ -76,10 +77,14 @@ } } - if (!empty($userid) && $unconfirm) { - Sql_Query(sprintf('update %s set confirmed = 0 where id = %d', + if (!empty($userid) && ($unconfirm || $confirm)) { + Sql_Query(sprintf('update %s set confirmed = ' . ($confirm ? '1' : '0') . ' where id = %d', $tables['user'], $userid)); - $actionresult .= sprintf($GLOBALS['I18N']->get('Made subscriber %s unconfirmed').'
', $userid); + if ($confirm) { + $actionresult .= sprintf(s('Made subscriber %s confirmed').'
', $userid); + } else { + $actionresult .= sprintf(s('Made subscriber %s unconfirmed').'
', $userid); + } } if (!empty($userid) && $maketext) { @@ -164,16 +169,28 @@ $GLOBALS['I18N']->get('Memo for this rule')); $newruleform .= '

'; $newruleform .= ''; - $actionpanel = ''; $actionpanel .= '
'; $actionpanel .= ''; $actionpanel .= ''; $actionpanel .= ''; $actionpanel .= ''; - $actionpanel .= ''; + list($msgDetails, $userDetails, $furtherDetails) = array('', '', ''); + if (preg_match("#bounced list message ([\d]+)#", $bounce['status'], $regs)) + $msgDetails = s('Campaign') . ' ' . PageLink2('message&id='.$regs[1], shortenTextDisplay(campaignTitle($regs[1]), 30)); + if (isset($guessedid)) { + $userDetails = Sql_Fetch_Assoc_Query(sprintf('select confirmed from %s where id = %d', $tables['user'], $guessedid)); + if ($userDetails['confirmed'] && !isBlackListed(htmlspecialchars($guessedemail))) + $ls_confirmed = $GLOBALS['img_tick']; + else + $ls_confirmed = $GLOBALS['img_cross']; + $userDetails = s('User') . ' ' . PageLink2('user&id='.$guessedid, $guessedid) . ' ' . $ls_confirmed . '
(' . s('Subscribers with a red icon are either unconfirmed or blacklisted or both') . ')'; + } + if (!empty($msgDetails) || !empty($userDetails)) + $furtherDetails = '
' . $msgDetails . (empty($msgDetails) ? '' : ' ') . $userDetails; + $actionpanel .= ''; $actionpanel .= ''; - $actionpanel .= ''; + $actionpanel .= ''; $actionpanel .= ''; $actionpanel .= ''; if (ALLOW_DELETEBOUNCE) { diff --git a/public_html/lists/admin/bouncemgt.php b/public_html/lists/admin/bouncemgt.php index d27dcd7b5..734661f1a 100755 --- a/public_html/lists/admin/bouncemgt.php +++ b/public_html/lists/admin/bouncemgt.php @@ -14,6 +14,7 @@ echo $spb.PageLink2('checkbouncerules', s('Check Current Bounce Rules')).$spe; echo $spb.PageLink2('processbounces', s('Process Bounces')).$spe; +echo $spb.PageLink2('processbounces&justexisting=true', s('Reprocess Only Existing Bounces'), '', false, s('Reprocess Only Existing Bounces')).$spe; echo '
'; @@ -24,3 +25,4 @@ echo '

'.s('You have already defined bounce rules in your system. Be careful with generating new ones, because these may interfere with the ones that exist.').'

'; } echo '

'.PageLink2('generatebouncerules', s('Generate Bounce Rules')).'

'; + diff --git a/public_html/lists/admin/bounces.php b/public_html/lists/admin/bounces.php index 95bda2cd4..e7442b6f5 100644 --- a/public_html/lists/admin/bounces.php +++ b/public_html/lists/admin/bounces.php @@ -162,7 +162,8 @@ break; } -} +} elseif ($status == 'processed') + echo s('Subscribers with a red icon are either unconfirmed or blacklisted or both'); $ls = new WebblerListing(s($status).' '.s('bounces')); $ls->setElementHeading('Bounce ID'); @@ -195,7 +196,13 @@ preg_match("#([\d]+) bouncecount increased#", $bounce['comment'], $regs) OR preg_match("#([\d]+) marked unconfirmed#", $bounce['comment'], $regs) ) { - $userIdLink = PageLink2('user&id='.$regs[1], getUserEmail($regs[1])); + $userIdLink = PageLink2('user&id='.$regs[1], ($regs[1])); + $userDetails = Sql_Fetch_Assoc_Query(sprintf('select email, confirmed from %s where id = %d', $tables['user'], $regs[1])); + if ($userDetails['confirmed'] && !isBlackListed(htmlspecialchars($userDetails['email']))) + $ls_confirmed = $GLOBALS['img_tick']; + else + $ls_confirmed = $GLOBALS['img_cross']; + $userIdLink .= ' ' . $ls_confirmed; } elseif ($bounce['comment'] == 'not processed') { $userIdLink = $GLOBALS['I18N']->get('Unknown'); } else { diff --git a/public_html/lists/admin/connect.php b/public_html/lists/admin/connect.php index f63b7cba9..6dc9900c5 100644 --- a/public_html/lists/admin/connect.php +++ b/public_html/lists/admin/connect.php @@ -1262,10 +1262,10 @@ function PageLinkDialogOnly($name, $desc = '', $url = '', $extraclass = '') return $link; } -function PageLinkAjax($name, $desc = '', $url = '', $extraclass = '') +function PageLinkAjax($name, $desc = '', $url = '', $extraclass = '', $title = '') { //# as PageLink2, but add the option to ajax it in a popover window - $link = PageLink2($name, $desc, $url); + $link = PageLink2($name, $desc, $url, false, $title ?: $desc); if ($link) { $link = str_replace(' 'text', 'category' => 'security', ), - + 'notify_admin_login' => array( + 'value' => 1, + 'description' => s('Notify admin on login from new location'), + 'type' => 'boolean', + 'category' => 'security', + 'allowempty' => true, + ), // admin addresses are other people who receive copies of subscriptions 'admin_addresses' => array( 'value' => '', diff --git a/public_html/lists/admin/import1.php b/public_html/lists/admin/import1.php index 827353ea4..fd45f0cbd 100644 --- a/public_html/lists/admin/import1.php +++ b/public_html/lists/admin/import1.php @@ -134,8 +134,8 @@ if (count($email_list) > 300 && !$test_import) { // this is a possibly a time consuming process, so lets show a progress bar flush(); - // increase the memory to make sure we are not running out - ini_set('memory_limit', '16M'); + // try to increase the memory to make sure we are not running out + @ini_set('memory_limit', '16M'); } // View test output of emails diff --git a/public_html/lists/admin/import2.php b/public_html/lists/admin/import2.php index df72bbf6f..f5d5ccd21 100644 --- a/public_html/lists/admin/import2.php +++ b/public_html/lists/admin/import2.php @@ -309,16 +309,21 @@ //@@ Why is $attributes not used $query = sprintf('select id from %s where name = "%s"', $tables['attribute'], sql_escape($column)); $existing = Sql_Fetch_Row_Query($query); - $_SESSION['import_attribute'][$column] = array( - 'index' => $i, - 'record' => $existing[0], - 'column' => $column, - ); - array_push($used_attributes, $existing[0]); - if ($existing[0]) { + if (!empty($existing[0])) { // $dbg .= " =known attribute id=" . $existing[0]; - } else { + $_SESSION['import_attribute'][$column] = array( + 'index' => $i, + 'record' => $existing[0], + 'column' => $column, + ); + array_push($used_attributes, $existing[0]); + } else { // $dbg .= " =request mapping"; + $_SESSION['import_attribute'][$column] = array( + 'index' => $i, + 'record' => "", + 'column' => $column, + ); } } } diff --git a/public_html/lists/admin/inc/UUID.old..php b/public_html/lists/admin/inc/UUID.old..php deleted file mode 100644 index 1e0a2ce73..000000000 --- a/public_html/lists/admin/inc/UUID.old..php +++ /dev/null @@ -1,156 +0,0 @@ -bytes = $uuid; - + // Optimize the most common use - $this->string = bin2hex(substr($uuid, 0, 4)).'-'. - bin2hex(substr($uuid, 4, 2)).'-'. - bin2hex(substr($uuid, 6, 2)).'-'. - bin2hex(substr($uuid, 8, 2)).'-'. + $this->string = bin2hex(substr($uuid, 0, 4)) . "-" . + bin2hex(substr($uuid, 4, 2)) . "-" . + bin2hex(substr($uuid, 6, 2)) . "-" . + bin2hex(substr($uuid, 8, 2)) . "-" . bin2hex(substr($uuid, 10, 6)); - } + // Store UUID in an optimized way + $this->uuid_ordered = bin2hex(substr($uuid, 6, 2)) . + bin2hex(substr($uuid, 4, 2)) . + bin2hex(substr($uuid, 0, 4)); + } + + /** - * @param int $ver + * @param int $ver * @param string $node * @param string $ns - * * @return Uuid - * * @throws Exception */ public static function generate($ver = 1, $node = null, $ns = null) { /* Create a new UUID based on provided data. */ - switch ((int) $ver) { + switch ((int)$ver) { case 1: return new static(static::mintTime($node)); case 2: @@ -170,106 +168,80 @@ public static function generate($ver = 1, $node = null, $ns = null) throw new Exception('Selected version is invalid or unsupported.'); } } - + /** * Generates a Version 1 UUID. * These are derived from the time at which they were generated. * * @param string $node - * * @return string */ protected static function mintTime($node = null) { - + /** Get time since Gregorian calendar reform in 100ns intervals * This is exceedingly difficult because of PHP's (and pack()'s) * integer size limits. * Note that this will never be more accurate than to the microsecond. */ $time = microtime(1) * 10000000 + static::INTERVAL; - + // Convert to a string representation - $time = sprintf('%F', $time); - + $time = sprintf("%F", $time); + //strip decimal point preg_match("/^\d+/", $time, $time); - + // And now to a 64-bit binary representation $time = base_convert($time[0], 10, 16); - $time = pack('H*', str_pad($time, 16, '0', STR_PAD_LEFT)); - + $time = pack("H*", str_pad($time, 16, "0", STR_PAD_LEFT)); + // Reorder bytes to their proper locations in the UUID - $uuid = $time[4].$time[5].$time[6].$time[7].$time[2].$time[3].$time[0].$time[1]; - + $uuid = $time[4] . $time[5] . $time[6] . $time[7] . $time[2] . $time[3] . $time[0] . $time[1]; + // Generate a random clock sequence $uuid .= static::randomBytes(2); - + // set variant $uuid[8] = chr(ord($uuid[8]) & static::CLEAR_VAR | static::VAR_RFC); - + // set version $uuid[6] = chr(ord($uuid[6]) & static::CLEAR_VER | static::VERSION_1); - + // Set the final 'node' parameter, a MAC address if (!is_null($node)) { $node = static::makeBin($node, 6); } - + // If no node was provided or if the node was invalid, // generate a random MAC address and set the multicast bit if (is_null($node)) { $node = static::randomBytes(6); - $node[0] = pack('C', ord($node[0]) | 1); + $node[0] = pack("C", ord($node[0]) | 1); } - + $uuid .= $node; - + return $uuid; } - + /** - * Randomness is returned as a string of bytes. + * Randomness is returned as a string of bytes * * @param $bytes - * * @return string */ public static function randomBytes($bytes) { - return call_user_func(array('static', static::initRandom()), $bytes); - } - - /** - * Trying for php 7 secure random generator, falling back to openSSL and Mcrypt. - * If none of the above is found, falls back to mt_rand - * Since laravel 4.* and 5.0 requires Mcrypt and 5.1 requires OpenSSL the fallback should never be used. - * - * @throws Exception - * - * @return string - */ - public static function initRandom() - { - if (function_exists('random_bytes')) { - return 'randomPhp7'; - } elseif (function_exists('openssl_random_pseudo_bytes')) { - return 'randomOpenSSL'; - } elseif (function_exists('mcrypt_encrypt')) { - return 'randomMcrypt'; - } - - // This is not the best randomizer (using mt_rand)... - return 'randomTwister'; + return random_bytes($bytes); } - + /** * Insure that an input string is either binary or hexadecimal. * Returns binary representation, or false on failure. * * @param string $str - * @param int $len - * + * @param integer $len * @return string|null */ protected static function makeBin($str, $len) @@ -288,10 +260,10 @@ protected static function makeBin($str, $len) if (strlen($str) !== ($len * 2)) { return null; } else { - return pack('H*', $str); + return pack("H*", $str); } } - + /** * Generates a Version 3 or Version 5 UUID. * These are derived from a hash of a name and its namespace, in binary form. @@ -299,9 +271,7 @@ protected static function makeBin($str, $len) * @param string $ver * @param string $node * @param string $ns - * * @return string - * * @throws Exception */ protected static function mintName($ver, $node, $ns) @@ -309,42 +279,42 @@ protected static function mintName($ver, $node, $ns) if (empty($node)) { throw new Exception('A name-string is required for Version 3 or 5 UUIDs.'); } - + // if the namespace UUID isn't binary, make it so $ns = static::makeBin($ns, 16); if (is_null($ns)) { throw new Exception('A binary namespace is required for Version 3 or 5 UUIDs.'); } - + $version = null; $uuid = null; - + switch ($ver) { case static::MD5: $version = static::VERSION_3; - $uuid = md5($ns.$node, 1); + $uuid = md5($ns . $node, 1); break; case static::SHA1: $version = static::VERSION_5; - $uuid = substr(sha1($ns.$node, 1), 0, 16); + $uuid = substr(sha1($ns . $node, 1), 0, 16); break; default: // no default really required here } - + // set variant $uuid[8] = chr(ord($uuid[8]) & static::CLEAR_VAR | static::VAR_RFC); - + // set version $uuid[6] = chr(ord($uuid[6]) & static::CLEAR_VER | $version); - - return $uuid; + + return ($uuid); } - + /** * Generate a Version 4 UUID. * These are derived solely from random numbers. - * generate random fields. + * generate random fields * * @return string */ @@ -355,22 +325,21 @@ protected static function mintRand() $uuid[8] = chr(ord($uuid[8]) & static::CLEAR_VAR | static::VAR_RFC); // set version $uuid[6] = chr(ord($uuid[6]) & static::CLEAR_VER | static::VERSION_4); - + return $uuid; } - + /** - * Import an existing UUID. + * Import an existing UUID * * @param string $uuid - * * @return Uuid */ public static function import($uuid) { return new static(static::makeBin($uuid, 16)); } - + /** * Compares the binary representations of two UUIDs. * The comparison will return true if they are bit-exact, @@ -378,7 +347,6 @@ public static function import($uuid) * * @param string $a * @param string $b - * * @return string|string */ public static function compare($a, $b) @@ -389,130 +357,73 @@ public static function compare($a, $b) return false; } } - - /** - * Get the specified number of random bytes, using random_bytes(). - * Randomness is returned as a string of bytes. - * - * Requires Php 7, or random_compact polyfill - * - * @param $bytes - * - * @return mixed - */ - protected static function randomPhp7($bytes) - { - return random_bytes($bytes); - } - - /** - * Get the specified number of random bytes, using openssl_random_pseudo_bytes(). - * Randomness is returned as a string of bytes. - * - * @param $bytes - * - * @return mixed - */ - protected static function randomOpenSSL($bytes) - { - return openssl_random_pseudo_bytes($bytes); - } - - /** - * Get the specified number of random bytes, using mcrypt_create_iv(). - * Randomness is returned as a string of bytes. - * - * @param $bytes - * - * @return string - */ - protected static function randomMcrypt($bytes) - { - return mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM); - } - - /** - * Get the specified number of random bytes, using mt_rand(). - * Randomness is returned as a string of bytes. - * - * @param int $bytes - * - * @return string - */ - protected static function randomTwister($bytes) - { - $rand = ''; - for ($a = 0; $a < $bytes; ++$a) { - $rand .= chr(mt_rand(0, 255)); - } - - return $rand; - } - + /** * @param string $var - * - * @return string|string|number|number|number|number|number|null|number|null|null + * @return string|string|number|number|number|number|number|NULL|number|NULL|NULL */ public function __get($var) { switch ($var) { - case 'bytes': + case "bytes": return $this->bytes; - // no break - case 'hex': + break; + case "hex": return bin2hex($this->bytes); - // no break - case 'string': - return $this->__toString(); - // no break - case 'urn': - return 'urn:uuid:'.$this->__toString(); - // no break - case 'version': - return ord($this->bytes[6]) >> 4; - // no break - case 'variant': - $byte = ord($this->bytes[8]); - if ($byte >= static::VAR_RES) { - return 3; - } elseif ($byte >= static::VAR_MS) { - return 2; - } elseif ($byte >= static::VAR_RFC) { - return 1; - } else { - return 0; - } - // no break - case 'node': + break; + case "node": if (ord($this->bytes[6]) >> 4 == 1) { return bin2hex(substr($this->bytes, 10)); } else { return null; } - // no break - case 'time': + break; + case "string": + return $this->__toString(); + break; + case "uuid_ordered": + return $this->__toUuidOrdered(); + break; + case "time": if (ord($this->bytes[6]) >> 4 == 1) { // Restore contiguous big-endian byte order - $time = bin2hex($this->bytes[6].$this->bytes[7].$this->bytes[4].$this->bytes[5]. - $this->bytes[0].$this->bytes[1].$this->bytes[2].$this->bytes[3]); + $time = bin2hex($this->bytes[6] . $this->bytes[7] . $this->bytes[4] . $this->bytes[5] . + $this->bytes[0] . $this->bytes[1] . $this->bytes[2] . $this->bytes[3]); // Clear version flag - $time[0] = '0'; - + $time[0] = "0"; + // Do some reverse arithmetic to get a Unix timestamp return (hexdec($time) - static::INTERVAL) / 10000000; } else { return null; } - // no break + break; + case "urn": + return "urn:uuid:" . $this->__toString(); + break; + case "variant": + $byte = ord($this->bytes[8]); + if ($byte >= static::VAR_RES) { + return 3; + } elseif ($byte >= static::VAR_MS) { + return 2; + } elseif ($byte >= static::VAR_RFC) { + return 1; + } else { + return 0; + } + break; + case "version": + return ord($this->bytes[6]) >> 4; + break; default: return null; - // no break + break; } } - + /** - * Return the UUID. + * Return the UUID * * @return string */ @@ -520,4 +431,26 @@ public function __toString() { return $this->string; } + + /** + * Return the UUID ORDERED + * + * @return uuid ordered + */ + public function __toUuidOrdered() + { + return $this->uuid_ordered; + } + + /** + * Import and validate an UUID + * + * @param Uuid|string $uuid + * + * @return boolean + */ + public static function validate($uuid) + { + return (boolean) preg_match('~' . static::VALID_UUID_REGEX . '~', static::import($uuid)->string); + } } diff --git a/public_html/lists/admin/inc/UUID2.php b/public_html/lists/admin/inc/UUID2.php deleted file mode 100644 index 29bf7d5b3..000000000 --- a/public_html/lists/admin/inc/UUID2.php +++ /dev/null @@ -1,524 +0,0 @@ -bytes = $uuid; - - // Optimize the most common use - $this->string = bin2hex(substr($uuid, 0, 4)).'-'. - bin2hex(substr($uuid, 4, 2)).'-'. - bin2hex(substr($uuid, 6, 2)).'-'. - bin2hex(substr($uuid, 8, 2)).'-'. - bin2hex(substr($uuid, 10, 6)); - } - - /** - * @param int $ver - * @param string $node - * @param string $ns - * - * @return Uuid - * - * @throws Exception - */ - public static function generate($ver = 1, $node = null, $ns = null) - { - /* Create a new UUID based on provided data. */ - switch ((int) $ver) { - case 1: - return new static(static::mintTime($node)); - case 2: - // Version 2 is not supported - throw new Exception('Version 2 is unsupported.'); - case 3: - return new static(static::mintName(static::MD5, $node, $ns)); - case 4: - return new static(static::mintRand()); - case 5: - return new static(static::mintName(static::SHA1, $node, $ns)); - default: - throw new Exception('Selected version is invalid or unsupported.'); - } - } - - /** - * Generates a Version 1 UUID. - * These are derived from the time at which they were generated. - * - * @param string $node - * - * @return string - */ - protected static function mintTime($node = null) - { - - /** Get time since Gregorian calendar reform in 100ns intervals - * This is exceedingly difficult because of PHP's (and pack()'s) - * integer size limits. - * Note that this will never be more accurate than to the microsecond. - */ - $time = microtime(1) * 10000000 + static::INTERVAL; - - // Convert to a string representation - $time = sprintf('%F', $time); - - //strip decimal point - preg_match("/^\d+/", $time, $time); - - // And now to a 64-bit binary representation - $time = base_convert($time[0], 10, 16); - $time = pack('H*', str_pad($time, 16, '0', STR_PAD_LEFT)); - - // Reorder bytes to their proper locations in the UUID - $uuid = $time[4].$time[5].$time[6].$time[7].$time[2].$time[3].$time[0].$time[1]; - - // Generate a random clock sequence - $uuid .= static::randomBytes(2); - - // set variant - $uuid[8] = chr(ord($uuid[8]) & static::CLEAR_VAR | static::VAR_RFC); - - // set version - $uuid[6] = chr(ord($uuid[6]) & static::CLEAR_VER | static::VERSION_1); - - // Set the final 'node' parameter, a MAC address - if (!is_null($node)) { - $node = static::makeBin($node, 6); - } - - // If no node was provided or if the node was invalid, - // generate a random MAC address and set the multicast bit - if (is_null($node)) { - $node = static::randomBytes(6); - $node[0] = pack('C', ord($node[0]) | 1); - } - - $uuid .= $node; - - return $uuid; - } - - /** - * Randomness is returned as a string of bytes. - * - * @param $bytes - * - * @return string - */ - public static function randomBytes($bytes) - { - return call_user_func(array('static', static::initRandom()), $bytes); - } - - /** - * Trying for php 7 secure random generator, falling back to openSSL and Mcrypt. - * If none of the above is found, falls back to mt_rand - * Since laravel 4.* and 5.0 requires Mcrypt and 5.1 requires OpenSSL the fallback should never be used. - * - * @throws Exception - * - * @return string - */ - public static function initRandom() - { - if (function_exists('random_bytes')) { - return 'randomPhp7'; - } elseif (function_exists('openssl_random_pseudo_bytes')) { - return 'randomOpenSSL'; - } elseif (function_exists('mcrypt_encrypt')) { - return 'randomMcrypt'; - } - - // This is not the best randomizer (using mt_rand)... - return 'randomTwister'; - } - - /** - * Insure that an input string is either binary or hexadecimal. - * Returns binary representation, or false on failure. - * - * @param string $str - * @param int $len - * - * @return string|null - */ - protected static function makeBin($str, $len) - { - if ($str instanceof self) { - return $str->bytes; - } - if (strlen($str) === $len) { - return $str; - } else { - // strip URN scheme and namespace - $str = preg_replace('/^urn:uuid:/is', '', $str); - } - // strip non-hex characters - $str = preg_replace('/[^a-f0-9]/is', '', $str); - if (strlen($str) !== ($len * 2)) { - return null; - } else { - return pack('H*', $str); - } - } - - /** - * Generates a Version 3 or Version 5 UUID. - * These are derived from a hash of a name and its namespace, in binary form. - * - * @param string $ver - * @param string $node - * @param string $ns - * - * @return string - * - * @throws Exception - */ - protected static function mintName($ver, $node, $ns) - { - if (empty($node)) { - throw new Exception('A name-string is required for Version 3 or 5 UUIDs.'); - } - - // if the namespace UUID isn't binary, make it so - $ns = static::makeBin($ns, 16); - if (is_null($ns)) { - throw new Exception('A binary namespace is required for Version 3 or 5 UUIDs.'); - } - - $version = null; - $uuid = null; - - switch ($ver) { - case static::MD5: - $version = static::VERSION_3; - $uuid = md5($ns.$node, 1); - break; - case static::SHA1: - $version = static::VERSION_5; - $uuid = substr(sha1($ns.$node, 1), 0, 16); - break; - default: - // no default really required here - } - - // set variant - $uuid[8] = chr(ord($uuid[8]) & static::CLEAR_VAR | static::VAR_RFC); - - // set version - $uuid[6] = chr(ord($uuid[6]) & static::CLEAR_VER | $version); - - return $uuid; - } - - /** - * Generate a Version 4 UUID. - * These are derived solely from random numbers. - * generate random fields. - * - * @return string - */ - protected static function mintRand() - { - $uuid = static::randomBytes(16); - // set variant - $uuid[8] = chr(ord($uuid[8]) & static::CLEAR_VAR | static::VAR_RFC); - // set version - $uuid[6] = chr(ord($uuid[6]) & static::CLEAR_VER | static::VERSION_4); - - return $uuid; - } - - /** - * Import an existing UUID. - * - * @param string $uuid - * - * @return Uuid - */ - public static function import($uuid) - { - return new static(static::makeBin($uuid, 16)); - } - - /** - * Compares the binary representations of two UUIDs. - * The comparison will return true if they are bit-exact, - * or if neither is valid. - * - * @param string $a - * @param string $b - * - * @return string|string - */ - public static function compare($a, $b) - { - if (static::makeBin($a, 16) == static::makeBin($b, 16)) { - return true; - } else { - return false; - } - } - - /** - * Get the specified number of random bytes, using random_bytes(). - * Randomness is returned as a string of bytes. - * - * Requires Php 7, or random_compact polyfill - * - * @param $bytes - * - * @return mixed - */ - protected static function randomPhp7($bytes) - { - return random_bytes($bytes); - } - - /** - * Get the specified number of random bytes, using openssl_random_pseudo_bytes(). - * Randomness is returned as a string of bytes. - * - * @param $bytes - * - * @return mixed - */ - protected static function randomOpenSSL($bytes) - { - return openssl_random_pseudo_bytes($bytes); - } - - /** - * Get the specified number of random bytes, using mcrypt_create_iv(). - * Randomness is returned as a string of bytes. - * - * @param $bytes - * - * @return string - */ - protected static function randomMcrypt($bytes) - { - return mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM); - } - - /** - * Get the specified number of random bytes, using mt_rand(). - * Randomness is returned as a string of bytes. - * - * @param int $bytes - * - * @return string - */ - protected static function randomTwister($bytes) - { - $rand = ''; - for ($a = 0; $a < $bytes; ++$a) { - $rand .= chr(mt_rand(0, 255)); - } - - return $rand; - } - - /** - * @param string $var - * - * @return string|string|number|number|number|number|number|null|number|null|null - */ - public function __get($var) - { - switch ($var) { - case 'bytes': - return $this->bytes; - // no break - case 'hex': - return bin2hex($this->bytes); - // no break - case 'string': - return $this->__toString(); - // no break - case 'urn': - return 'urn:uuid:'.$this->__toString(); - // no break - case 'version': - return ord($this->bytes[6]) >> 4; - // no break - case 'variant': - $byte = ord($this->bytes[8]); - if ($byte >= static::VAR_RES) { - return 3; - } elseif ($byte >= static::VAR_MS) { - return 2; - } elseif ($byte >= static::VAR_RFC) { - return 1; - } else { - return 0; - } - // no break - case 'node': - if (ord($this->bytes[6]) >> 4 == 1) { - return bin2hex(substr($this->bytes, 10)); - } else { - return null; - } - // no break - case 'time': - if (ord($this->bytes[6]) >> 4 == 1) { - // Restore contiguous big-endian byte order - $time = bin2hex($this->bytes[6].$this->bytes[7].$this->bytes[4].$this->bytes[5]. - $this->bytes[0].$this->bytes[1].$this->bytes[2].$this->bytes[3]); - // Clear version flag - $time[0] = '0'; - - // Do some reverse arithmetic to get a Unix timestamp - return (hexdec($time) - static::INTERVAL) / 10000000; - } else { - return null; - } - // no break - default: - return null; - // no break - } - } - - /** - * Return the UUID. - * - * @return string - */ - public function __toString() - { - return $this->string; - } -} diff --git a/public_html/lists/admin/inc/userlib.php b/public_html/lists/admin/inc/userlib.php index da8eb273c..00b9ea5aa 100644 --- a/public_html/lists/admin/inc/userlib.php +++ b/public_html/lists/admin/inc/userlib.php @@ -196,14 +196,14 @@ function addNewUser($email, $password = '') // insert into user db $exists = Sql_Fetch_Row_Query(sprintf('select id from %s where email = "%s"', $GLOBALS['tables']['user'], $email)); - if ($exists[0]) { + if (!empty($exists[0])) { return $exists[0]; } $blacklist = isBlackListed($email); $passwordEnc = encryptPass($password); Sql_Query(sprintf('insert into %s set email = "%s", blacklisted = "%d", - entered = now(),modified = now(),password = "%s", + entered = now(),password = "%s", passwordchanged = now(),disabled = 0, uniqid = "%s",htmlemail = 1, uuid = "%s" ', $GLOBALS['tables']['user'], sql_escape($email), $blacklist, $passwordEnc, getUniqid(), (string) uuid::generate(4))); @@ -840,7 +840,7 @@ function addUserHistory($email, $msg, $detail) } $userid = Sql_Fetch_Row_Query("select id from $user_table where email = \"$email\""); - if ($userid[0]) { + if (!empty($userid[0])) { Sql_Query(sprintf('insert into %s (ip,userid,date,summary,detail,systeminfo) values("%s",%d,now(),"%s","%s","%s")', $user_his_table, getClientIP(), $userid[0], sql_escape($msg), sql_escape(htmlspecialchars($detail)), sql_escape($sysinfo))); diff --git a/public_html/lists/admin/index.php b/public_html/lists/admin/index.php index fdcb7182f..c747423e7 100644 --- a/public_html/lists/admin/index.php +++ b/public_html/lists/admin/index.php @@ -290,6 +290,7 @@ function mb_strtolower($string) } echo "$page_title"; $inRemoteCall = false; +$doLoginCheck = Sql_Table_exists($tables['admin_login']); if (!empty($GLOBALS['require_login'])) { //bth 7.1.2015 to support x-forwarded-for @@ -312,6 +313,11 @@ function mb_strtolower($string) $msg = $loginresult[1]; } else { session_regenerate_id(); + if ($doLoginCheck) { + # invalidate other active sessions + Sql_Query(sprintf('update %s set active = 0 where adminid = %d and active != 0',$GLOBALS['tables']['admin_login'],$loginresult[0])); + } + $_SESSION['adminloggedin'] = $remoteAddr; $_SESSION['logindetails'] = array( 'adminname' => $_REQUEST['login'], @@ -325,6 +331,17 @@ function mb_strtolower($string) if (!empty($_POST['page'])) { $page = preg_replace('/\W+/', '', $_POST['page']); } + + if ($doLoginCheck) { + # check if this is a new IP address + $knownIP = Sql_Fetch_Row_Query(sprintf('select * from %s where remote_ip4 = "%s" and adminid = %d ',$GLOBALS['tables']['admin_login'],$remoteAddr,$loginresult[0])); + if (empty($knownIP[0])) { + notifyNewIPLogin($loginresult[0]); + } + Sql_Query(sprintf('insert into %s (moment,adminid,remote_ip4,remote_ip6,sessionid,active) + values(%d,%d,"%s","%s","%s",1)', + $GLOBALS['tables']['admin_login'],time(),$loginresult[0],$remoteAddr,"",session_id())); + } } //If passwords are encrypted and a password recovery request was made, send mail to the admin of the given email address. } elseif (isset($_REQUEST['forgotpassword'])) { @@ -373,14 +390,24 @@ function mb_strtolower($string) $_SESSION['logindetails'] = ''; $page = 'login'; } elseif ($_SESSION['adminloggedin'] && $_SESSION['logindetails']) { + if ($doLoginCheck) { + $active = Sql_Fetch_Row_Query(sprintf('select active from %s where adminid = %d and (remote_ip4 = "%s" or remote_ip6 = "%s") and sessionid = "%s"', + $GLOBALS['tables']['admin_login'],$_SESSION['logindetails']['id'],$remoteAddr,"",session_id())); + } else { + $active = array(1); ## pretend to be active + } $validate = $GLOBALS['admin_auth']->validateAccount($_SESSION['logindetails']['id']); - if (!$validate[0]) { + if (empty($active[0]) || !$validate[0]) { logEvent(sprintf($GLOBALS['I18N']->get('invalidated login from %s for %s (error %s)'), $remoteAddr, $_SESSION['logindetails']['adminname'], $validate[1])); $_SESSION['adminloggedin'] = ''; $_SESSION['logindetails'] = ''; $page = 'login'; - $msg = $validate[1]; + if (empty($active[0])) { + $msg = s('Your session was invalidated by a new session in a different browser'); + } else { + $msg = $validate[1]; + } } } else { $page = 'login'; diff --git a/public_html/lists/admin/initialise.php b/public_html/lists/admin/initialise.php index 35a5b5b05..c739709a8 100644 --- a/public_html/lists/admin/initialise.php +++ b/public_html/lists/admin/initialise.php @@ -167,8 +167,8 @@ function output($message) $_SESSION['firstinstall'] = 1; $adminemail = $_REQUEST['adminemail']; $adminpass = $_REQUEST['adminpassword']; - Sql_Query(sprintf('insert into %s (loginname,namelc,email,created,modified,password,passwordchanged,superuser,disabled) - values("%s","%s","%s",now(),now(),"%s",now(),%d,0)', + Sql_Query(sprintf('insert into %s (loginname,namelc,email,created,password,passwordchanged,superuser,disabled) + values("%s","%s","%s",now(),"%s",now(),%d,0)', $tables['admin'], 'admin', 'admin', sql_escape($adminemail), encryptPass($adminpass), 1)); //# let's add them as a subscriber as well diff --git a/public_html/lists/admin/languages.php b/public_html/lists/admin/languages.php index 183a21579..6782a5f21 100644 --- a/public_html/lists/admin/languages.php +++ b/public_html/lists/admin/languages.php @@ -377,12 +377,10 @@ public function formatText($text) if (isset($GLOBALS['developer_email'])) { if (!empty($_SESSION['show_translation_colours'])) { - return ''.str_replace("\n", '', $text).''; + return ''.$text.''; } -// return 'TE'.$text.'XT'; } -// return ''.str_replace("\n","",$text).''; - return str_replace("\n", '', $text); + return $text; } /** diff --git a/public_html/lists/admin/lib.php b/public_html/lists/admin/lib.php index 8862710ef..b4236ef0c 100644 --- a/public_html/lists/admin/lib.php +++ b/public_html/lists/admin/lib.php @@ -30,6 +30,7 @@ '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'), + 'decreasecountconfirmuseranddeletebounce' => $GLOBALS['I18N']->get('decrease count and confirm subscriber and delete bounce'), 'deletebounce' => $GLOBALS['I18N']->get('delete bounce'), ); @@ -942,7 +943,7 @@ function getPageLock($force = 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) { + if (!empty($running_res['age']) && (int)$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 @@ -1878,7 +1879,7 @@ function refreshTlds($force = 0) $lastDone = getConfig('tld_last_sync'); $tlds = ''; //# let's not do this too often - if ($lastDone + TLD_REFETCH_TIMEOUT < time() || $force) { + if (((int)$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')) { @@ -2486,3 +2487,39 @@ function getClientIP() return $the_ip; } + + +function notifyNewIPLogin($adminId) { + + $enabled = getConfig('notify_admin_login'); + if (empty($enabled)) { + return; + } + $msg = s(' + +-------------------------------------------------------------------------------- + +We noticed a login to your phpList installation at https://%s +from a new location. If this was you, you can delete this message. +If you do not recognise this, please login to your phpList installation +and change your password. + +-------------------------------------------------------------------------------- ', +$GLOBALS['config']['website']); + + $admin_mail = $GLOBALS['admin_auth']->adminEmail($_SESSION['logindetails']['id']); + $ok = sendMail($admin_mail, $GLOBALS['installation_name'].' '.s('login from new location'), $msg, system_messageheaders($admin_mail)); + + if ($ok === 0) { + $main_admin_mail = getConfig('admin_address'); + logEvent(sprintf('Error sending login notification to %s', $admin_mail)); + + $msg = s(' +--------------------- + phpList tried sending the below message to '.$admin_mail.' + but this failed. +------------------').PHP_EOL.PHP_EOL.$msg; + sendMail($main_admin_mail, $GLOBALS['installation_name'].' '.s('login from new location'), $msg, system_messageheaders($admin_mail)); + } + +} \ No newline at end of file diff --git a/public_html/lists/admin/listbounces.php b/public_html/lists/admin/listbounces.php index bc890deb2..238059ad7 100644 --- a/public_html/lists/admin/listbounces.php +++ b/public_html/lists/admin/listbounces.php @@ -103,14 +103,22 @@ if ($download) { echo $userdata['email']."\n"; } else { + if ($userdata['confirmed'] && !isBlackListed(htmlspecialchars($userdata['email']))) { + $ls_confirmed = $GLOBALS['img_tick']; + } else { + $ls_confirmed = $GLOBALS['img_cross']; + } $ls->addElement($row['userid'], PageUrl2('user&id='.$row['userid'])); - $ls->addColumn($row['userid'], s('Subscriber address'), PageLink2('user&id='.$row['userid'], $userdata['email'])); + $ls->addColumn($row['userid'], s('Subscriber address'), PageLink2('user&id='.$row['userid'], $userdata['email']) . ' ' . $ls_confirmed); $ls->addColumn($row['userid'], s('Total bounces'), PageLink2('user&id='.$row['userid'], $row['numbounces'])); } } } if (!$download) { + if ($total) { + echo s('Subscribers with a red icon are either unconfirmed or blacklisted or both'); + } echo $ls->display(); } else { exit; diff --git a/public_html/lists/admin/locale/en/phplist.po b/public_html/lists/admin/locale/en/phplist.po index 24e93d545..158de7626 100644 --- a/public_html/lists/admin/locale/en/phplist.po +++ b/public_html/lists/admin/locale/en/phplist.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: English (phpList)\n" "Report-Msgid-Bugs-To: info@phplist.com\n" "POT-Creation-Date: 2021-02-12 19:38+0000\n" -"PO-Revision-Date: 2023-02-23 08:29+0000\n" +"PO-Revision-Date: 2023-11-06 03:05+0000\n" "Last-Translator: Duncan Cameron \n" "Language-Team: English \n" @@ -3979,14 +3979,12 @@ msgid "Reply to" msgstr "reply-to" #: public_html/lists/admin/send_core.php:714 -#, fuzzy msgid "Message preview" -msgstr "Message ID" +msgstr "Message preview" #: public_html/lists/admin/send_core.php:717 -#, fuzzy msgid "Generate" -msgstr "Rate" +msgstr "Generate" #: public_html/lists/admin/send_core.php:726 msgid "Send a Webpage" diff --git a/public_html/lists/admin/locale/fr/phplist.po b/public_html/lists/admin/locale/fr/phplist.po index 364dbef3d..6135c9e5e 100644 --- a/public_html/lists/admin/locale/fr/phplist.po +++ b/public_html/lists/admin/locale/fr/phplist.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: French (phpList)\n" "Report-Msgid-Bugs-To: info@phplist.com\n" "POT-Creation-Date: 2021-02-12 19:03+0000\n" -"PO-Revision-Date: 2022-12-08 02:09+0000\n" +"PO-Revision-Date: 2024-02-07 13:38+0000\n" "Last-Translator: Alain Rihs \n" "Language-Team: French \n" @@ -2564,8 +2564,8 @@ msgid "" "This campaign is scheduled to stop sending before the embargo time. No mails " "will be sent." msgstr "" -"Cette campagne est programmée pour cesser d'envoyer avant le temps " -"d'embargo. Aucun courrier ne sera envoyé." +"Cette campagne est programmée pour ne rien envoyer avant le temps d'envoi " +"différé. Aucun courriel ne sera envoyé." #: public_html/databasestructure.php:126 msgid "Bounce ID" @@ -6203,7 +6203,7 @@ msgstr "Clics des abonnés pour une campagne" #: public_html/lists/admin/actions/msgstatus.php:62 #, php-format msgid "%s left until embargo" -msgstr "%s restant avant l'embargo" +msgstr "%s restant avant l'envoi différé" # Suggested in Weblate: Modèles de campagne #: public_html/lists/admin/templates.php:31 @@ -6535,7 +6535,7 @@ msgstr "Opération réussie" #: public_html/lists/admin/send_core.php:748 msgid "Embargoed Until" -msgstr "Bloqué jusqu'à cette période" +msgstr "Bloqué jusqu'à" #: public_html/databasestructure.php:300 msgid "pagetitlehover:getrss" @@ -7405,7 +7405,7 @@ msgstr "" #: public_html/databasestructure.php:354 msgid "pagetitlehover:updatetranslation" -msgstr "pagetitlehover:Traductions" +msgstr "Obtenez la dernière traduction du système de traduction communautaire" #: public_html/databasestructure.php:355 msgid "pagetitle:reindex" diff --git a/public_html/lists/admin/members.php b/public_html/lists/admin/members.php index f5abae282..bbfa07b98 100644 --- a/public_html/lists/admin/members.php +++ b/public_html/lists/admin/members.php @@ -22,6 +22,7 @@ $confirmedSelection = ' u.confirmed and !u.blacklisted'; } $listAll = false; +$subselect = ''; switch ($access) { case 'owner': diff --git a/public_html/lists/admin/messages.php b/public_html/lists/admin/messages.php index 880cdd444..4ffb2262f 100644 --- a/public_html/lists/admin/messages.php +++ b/public_html/lists/admin/messages.php @@ -215,9 +215,9 @@ $idToDuplicate = sprintf('%d', $_GET['duplicate']); $action_result .= $GLOBALS['I18N']->get('Copying')." $idToDuplicate .."; Sql_Query(sprintf('insert into %s (uuid, subject, fromfield, tofield, replyto, message, textmessage, footer, entered, - modified, embargo, repeatuntil, repeatinterval, requeueinterval, status, htmlformatted, sendformat, template, rsstemplate, owner) + embargo, repeatuntil, repeatinterval, requeueinterval, status, htmlformatted, sendformat, template, rsstemplate, owner) select "%s", subject, fromfield, tofield, replyto, message, textmessage, footer, now(), - now(), now(), now(), repeatinterval, requeueinterval, "draft", htmlformatted, + now(), now(), repeatinterval, requeueinterval, "draft", htmlformatted, sendformat, template, rsstemplate, "%d" from %s where id = %d', $GLOBALS['tables']['message'], (string) Uuid::generate(4), $_SESSION['logindetails']['id'],$GLOBALS['tables']['message'], diff --git a/public_html/lists/admin/mysqli.inc b/public_html/lists/admin/mysqli.inc index fbb6d258b..ab0e7a96c 100644 --- a/public_html/lists/admin/mysqli.inc +++ b/public_html/lists/admin/mysqli.inc @@ -77,7 +77,7 @@ function Sql_Connect($host, $user, $password, $database) print "Cannot connect to Database, please check your configuration"; exit; } - mysqli_query($db, "SET NAMES 'utf8'"); + mysqli_query($db, "SET NAMES 'utf8mb4'"); unset($GLOBALS['lastquery']); return $db; @@ -410,6 +410,9 @@ function Sql_create_Table($table, $structure) function sql_escape($text) { + if (empty($text)) { + return ''; + } if (empty($GLOBALS['database_connection'])) { $GLOBALS['database_connection'] = Sql_Connect( $GLOBALS['database_host'], diff --git a/public_html/lists/admin/phpListAdminAuthentication.php b/public_html/lists/admin/phpListAdminAuthentication.php index d2b26e765..aa6be616e 100644 --- a/public_html/lists/admin/phpListAdminAuthentication.php +++ b/public_html/lists/admin/phpListAdminAuthentication.php @@ -37,12 +37,15 @@ public function validateLogin($login, $password) $passwordDB = $admindata['password']; //Password encryption verification. if (strlen($passwordDB) < $GLOBALS['hash_length']) { // Passwords are encrypted but the actual is not. + return array(0, s('incorrect password')); + + // the below is actually insecure, it allows resetting the password without approval, so remove //Encrypt the actual DB password before performing the validation below. - $encryptedPassDB = hash(HASH_ALGO, $passwordDB); - $query = sprintf('update %s set password = "%s" where loginname = "%s"', $GLOBALS['tables']['admin'], - $encryptedPassDB, sql_escape($login)); - $passwordDB = $encryptedPassDB; - $req = Sql_Query($query); + // $encryptedPassDB = hash(HASH_ALGO, $passwordDB); + // $query = sprintf('update %s set password = "%s" where loginname = "%s"', $GLOBALS['tables']['admin'], + // $encryptedPassDB, sql_escape($login)); + // $passwordDB = $encryptedPassDB; + // $req = Sql_Query($query); } if ($admindata['disabled']) { @@ -138,7 +141,7 @@ public function adminName($id) { $req = Sql_Fetch_Row_Query(sprintf('select loginname from %s where id = %d', $GLOBALS['tables']['admin'], $id)); - return $req[0] ? $req[0] : s('Nobody'); + return !empty($req[0]) ? $req[0] : s('Nobody'); } /** diff --git a/public_html/lists/admin/pluginlib.php b/public_html/lists/admin/pluginlib.php index cc2d6d9c7..6f365b5a4 100644 --- a/public_html/lists/admin/pluginlib.php +++ b/public_html/lists/admin/pluginlib.php @@ -69,6 +69,8 @@ $GLOBALS['plugins_disabled'][] = $pl; } } +} else { + $disabled_plugins = array(); } //var_dump($GLOBALS['plugins_disabled']);exit; diff --git a/public_html/lists/admin/plugins.php b/public_html/lists/admin/plugins.php index 558c16da9..10a83cf71 100644 --- a/public_html/lists/admin/plugins.php +++ b/public_html/lists/admin/plugins.php @@ -310,8 +310,8 @@ if ($canEnable) { $ls->addColumn($pluginname, s('enabled'), $plugin->enabled ? $GLOBALS['img_tick'] : $GLOBALS['img_cross']); $ls->addColumn($pluginname, s('action'), $plugin->enabled ? - PageLinkAjax('plugins&disable='.$pluginname, '') : - PageLinkAjax('plugins&enable='.$pluginname, '')); + PageLinkAjax('plugins&disable='.$pluginname, '') : + PageLinkAjax('plugins&enable='.$pluginname, '')); } else { $ls->addColumn($pluginname, s('enabled'), $GLOBALS['img_cross']); } diff --git a/public_html/lists/admin/processbounces.php b/public_html/lists/admin/processbounces.php index 457ac7ec0..39fbdcdfa 100644 --- a/public_html/lists/admin/processbounces.php +++ b/public_html/lists/admin/processbounces.php @@ -505,26 +505,27 @@ function processMessages($link, $max) flushBrowser(); $download_report = ''; -switch ($bounce_protocol) { - case 'pop': - $download_report = processPop($bounce_mailbox_host, $bounce_mailbox_user, $bounce_mailbox_password); - break; - case 'mbox': - $download_report = processMbox($bounce_mailbox); - break; - default: - Error($GLOBALS['I18N']->get('bounce_protocol not supported')); +if (!isset($_GET['justexisting'])) { + switch ($bounce_protocol) { + case 'pop': + $download_report = processPop($bounce_mailbox_host, $bounce_mailbox_user, $bounce_mailbox_password); + break; + case 'mbox': + $download_report = processMbox($bounce_mailbox); + break; + default: + Error($GLOBALS['I18N']->get('bounce_protocol not supported')); - return; -} + return; + } -if ($GLOBALS['commandline'] && $download_report === false) { - cl_output(s('Download failed, exiting')); + if ($GLOBALS['commandline'] && $download_report === false) { + cl_output(s('Download failed, exiting')); - return; -} + return; + } // now we have filled database with all available bounces - +} //# reprocess the unidentified ones, as the bounce detection has improved, so it might catch more cl_output('reprocessing'); @@ -648,6 +649,22 @@ function processMessages($link, $max) } deleteBounce($row['bounce']); break; + case 'decreasecountconfirmuseranddeletebounce': + Sql_Query(sprintf('update %s set bouncecount = bouncecount -1 where id = %d', + $GLOBALS['tables']['user'], $row['user'])); + if (!$confirmed) { + logEvent('User ' . $userdata['email'] . ' confirmed by bounce rule ' . PageLink2('bouncerule&id=' . $rule['id'], + $rule['id'])); + Sql_Query(sprintf('update %s set confirmed = 1 where id = %d', $GLOBALS['tables']['user'], + $row['user'])); + $advanced_report .= 'User ' . $userdata['email'] . ' made confirmed by bounce rule ' . $rule['id'] . PHP_EOL; + $advanced_report .= 'User: ' . $report_linkroot . '/?page=user&id=' . $userdata['id'] . PHP_EOL; + $advanced_report .= 'Rule: ' . $report_linkroot . '/?page=bouncerule&id=' . $rule['id'] . PHP_EOL; + addUserHistory($userdata['email'], s('Auto confirmed'), + s('Subscriber auto confirmed for') . ' ' . $GLOBALS['I18N']->get('bounce rule') . ' ' . $rule['id']); + } + deleteBounce($row['bounce']); + break; case 'blacklistuser': if (!$blacklisted) { logEvent('User ' . $userdata['email'] . ' blacklisted by bounce rule ' . PageLink2('bouncerule&id=' . $rule['id'], diff --git a/public_html/lists/admin/send_core.php b/public_html/lists/admin/send_core.php index 28c0ca04f..012fa2cd7 100644 --- a/public_html/lists/admin/send_core.php +++ b/public_html/lists/admin/send_core.php @@ -211,7 +211,7 @@ subject = "%s", fromfield = "%s", tofield = "%s", replyto ="%s", embargo = "%s", repeatinterval = "%s", repeatuntil = "%s", message = "%s", textmessage = "%s", footer = "%s", status = "%s", - htmlformatted = "%s", sendformat = "%s", template = "%s" where id = %d', + htmlformatted = "%s", sendformat = "%s", template = "%d" where id = %d', $tables['message'], sql_escape(strip_tags($messagedata['campaigntitle'])), /* we store the title in the subject field. Better would be to rename the DB column, but this will do for now */ @@ -610,7 +610,7 @@ ++$counttabs; // print $tabs->display(); - } + } echo ''; if ($_GET['page'] == 'preparemessage') { @@ -1333,7 +1333,7 @@ $pluginerror = $plugin->allowMessageToBeQueued($messagedata); if ($pluginerror) { $allReady = false; - $pluginerror = preg_replace("/\n/", '', $pluginerror); + $pluginerror = str_replace(["\n", "'"], ['', "\\'"], $pluginerror); $GLOBALS['pagefooter']['addtoqueue'] .= ''; diff --git a/public_html/lists/admin/sendemaillib.php b/public_html/lists/admin/sendemaillib.php index 3243bc10e..587570aaf 100644 --- a/public_html/lists/admin/sendemaillib.php +++ b/public_html/lists/admin/sendemaillib.php @@ -14,6 +14,7 @@ function output($text) function sendEmail($messageid, $email, $hash, $htmlpref = 0, $rssitems = array(), $forwardedby = array()) { + global $admin_auth; $getspeedstats = VERBOSE && !empty($GLOBALS['getspeedstats']) && isset($GLOBALS['processqueue_timer']); $sqlCountStart = $GLOBALS['pagestats']['number_of_queries']; $isTestMail = isset($_GET['page']) && $_GET['page'] == 'send'; @@ -890,10 +891,20 @@ function sendEmail($messageid, $email, $hash, $htmlpref = 0, $rssitems = array() if ($hash != 'forwarded' || !count($forwardedby)) { $fromname = $cached[$messageid]['fromname']; - $subject = $cached[$messageid]['subject']; + $subject = (!$isTestMail ? '' : ($GLOBALS['I18N']->get('(test)')) . ' ') . $cached[$messageid]['subject']; if (!empty($cached[$messageid]['replytoemail'])) { $mail->AddReplyTo($cached[$messageid]['replytoemail'], $cached[$messageid]['replytoname']); + } elseif ($isTestMail) { + $testReplyAddress = $admin_auth->adminEmail($_SESSION['logindetails']['id']); + $testReplyName = $admin_auth->adminName($_SESSION['logindetails']['id']); + if (empty($testReplyAddress)) { + $testReplyAddress = getConfig('admin_address'); + $testReplyName = ''; + } + if (!empty($testReplyAddress)) { + $mail->AddReplyTo($testReplyAddress, $testReplyName); + } } } else { $fromname = $forwardedby['subscriberName']; @@ -1630,3 +1641,4 @@ function precacheMessage($messageid, $forwardContent = 0) $newpoweredimage, 70, 30)); } + diff --git a/public_html/lists/admin/structure.php b/public_html/lists/admin/structure.php index 703f3e9c1..2ed33fc28 100644 --- a/public_html/lists/admin/structure.php +++ b/public_html/lists/admin/structure.php @@ -46,7 +46,7 @@ 'optedin' => array('tinyint default 0', 'sysexp:Did this subscriber manually confirm'), 'bouncecount' => array('integer default 0', 'sysexp:Number of bounces'), 'entered' => array('datetime', 'sysexp:Entered'), - 'modified' => array('timestamp', 'sysexp:Last Modified'), + 'modified' => array('timestamp not null default current_timestamp on update current_timestamp', 'sysexp:Last Modified'), 'uniqid' => array('varchar(255)', 'sysexp:Unique ID'), 'uuid' => array('varchar(36) default ""', 'sys:UUID'), 'htmlemail' => array('tinyint default 0', 'Send this subscriber HTML emails'), @@ -89,7 +89,7 @@ 'listorder' => array('integer', 'Order of listing'), 'prefix' => array('varchar(10)', 'Subject prefix'), 'rssfeed' => array('varchar(255)', 'Rss Feed'), - 'modified' => array('timestamp', 'Modified'), + 'modified' => array('timestamp not null default current_timestamp on update current_timestamp', 'Modified'), 'active' => array('tinyint', 'Active'), 'owner' => array('integer', 'Admin who is owner of this list'), 'category' => array('varchar(255) default ""', 'List category'), @@ -108,7 +108,7 @@ 'userid' => array('integer not null', 'Subscriber ID'), 'listid' => array('integer not null', 'List ID'), 'entered' => array('datetime', 'Entered'), - 'modified' => array('timestamp', 'Modified'), + 'modified' => array('timestamp not null default current_timestamp on update current_timestamp', 'Modified'), 'primary key' => array('(userid,listid)', 'Primary Key'), 'index_1' => array('userenteredidx (userid,entered)', ''), 'index_2' => array('userlistenteredidx (userid,listid,entered)', ''), @@ -118,15 +118,15 @@ 'message' => array( // a message 'id' => array('integer not null primary key auto_increment', 'ID'), 'uuid' => array('varchar(36) default ""', 'UUID'), - 'subject' => array("varchar(255) not null default '(no subject)'", 'subject'), + 'subject' => array("varchar(255) character set utf8mb4 not null default '(no subject)'", 'subject'), 'fromfield' => array("varchar(255) not null default ''", 'from'), 'tofield' => array("varchar(255) not null default ''", 'tofield'), 'replyto' => array("varchar(255) not null default ''", 'reply-to'), - 'message' => array('longtext', 'Message'), - 'textmessage' => array('longtext', 'Text version of Message'), + 'message' => array('longtext character set utf8mb4', 'Message'), + 'textmessage' => array('longtext character set utf8mb4', 'Text version of Message'), 'footer' => array('text', 'Footer for a message'), 'entered' => array('datetime', 'Entered'), - 'modified' => array('timestamp', 'Modified'), + 'modified' => array('timestamp not null default current_timestamp on update current_timestamp', 'Modified'), 'embargo' => array('datetime', 'Time to send message'), 'repeatinterval' => array('integer default 0', 'Number of seconds to repeat the message'), 'repeatuntil' => array('datetime', 'Final time to stop repetition'), @@ -155,7 +155,7 @@ 'messagedata' => array( 'name' => array('varchar(100) not null', 'Name of field'), 'id' => array('integer not null', 'Message ID'), - 'data' => array('longtext', 'Data'), + 'data' => array('longtext character set utf8mb4', 'Data'), 'primary key' => array('(name,id)', ''), ), 'listmessage' => array( // linking messages to a list @@ -163,7 +163,7 @@ 'messageid' => array('integer not null', 'Message ID'), 'listid' => array('integer not null', 'List ID'), 'entered' => array('datetime', 'Entered'), - 'modified' => array('timestamp', 'Modified'), + 'modified' => array('timestamp not null default current_timestamp on update current_timestamp', 'Modified'), 'unique_1' => array('(messageid,listid)', ''), 'index_1' => array('listmessageidx (listid,messageid)', ''), ), @@ -223,7 +223,7 @@ 'sendprocess' => array( // keep track of running send processes to avoid to many running concurrently 'id' => array('integer not null primary key auto_increment', 'ID'), 'started' => array('datetime', 'Start Time'), - 'modified' => array('timestamp', 'Modified'), + 'modified' => array('timestamp not null default current_timestamp on update current_timestamp', 'Modified'), 'alive' => array('integer default 1', 'Is this process still alive?'), 'ipaddress' => array('varchar(50)', 'IP Address of who started it'), 'page' => array('varchar(100)', 'The page that this process runs in'), @@ -261,7 +261,7 @@ 'user' => array('integer not null', 'subscriber ID'), 'message' => array('integer not null', 'Message ID'), 'bounce' => array('integer not null', 'Bounce ID'), - 'time' => array('timestamp', 'When did it bounce'), + 'time' => array('timestamp not null default current_timestamp on update current_timestamp', 'When did it bounce'), 'index_1' => array('umbindex (user,message,bounce)', 'index'), 'index_2' => array('useridx (user)', 'index'), 'index_3' => array('msgidx (message)', 'index'), @@ -273,7 +273,7 @@ 'message' => array('integer not null', 'Message ID'), 'forward' => array('varchar(255)', 'Forward email'), 'status' => array('varchar(255)', 'Status of forward'), - 'time' => array('timestamp', 'When was it forwarded'), + 'time' => array('timestamp not null default current_timestamp on update current_timestamp', 'When was it forwarded'), 'index_1' => array('usermessageidx (user,message)', 'index'), 'index_2' => array('useridx (user)', 'index'), 'index_3' => array('messageidx (message)', 'index'), @@ -290,7 +290,7 @@ 'namelc' => array('varchar(255)', 'sys:Normalised loginname'), 'email' => array('varchar(255) not null', 'Email'), 'created' => array('datetime', 'sys:Time Created'), - 'modified' => array('timestamp', 'sys:Time modified'), + 'modified' => array('timestamp not null default current_timestamp on update current_timestamp', 'sys:Time modified'), 'modifiedby' => array('varchar(66)', 'sys:Modified by'), 'password' => array('varchar(255)', 'sys:Password'), 'passwordchanged' => array('date', 'sys:Last time password was changed'), @@ -321,7 +321,15 @@ 'admin' => array('integer', "Admin's Id"), 'key_value' => array('varchar (32) not null', 'Key'), ), - /* + 'admin_login' => array( + 'id' => array('integer not null primary key auto_increment', 'Id'), + 'moment' => array('bigint not null', 'epoch when it happened'), + 'adminid' => array('integer not null', "Admin Id"), + 'remote_ip4' => array('varchar (32) not null', 'IPv4 address'), + 'remote_ip6' => array('varchar (50) not null', 'IPv6 address'), + 'sessionid' => array('varchar (50) not null', 'Session ID'), + 'active' => array('tinyint not null default 0', 'is this login active'), + ), /* * obsolete tables "task" => array( "id" => array("integer not null primary key auto_increment","ID"), diff --git a/public_html/lists/admin/subscribelib2.php b/public_html/lists/admin/subscribelib2.php index cc01f1dfb..cc014e97a 100644 --- a/public_html/lists/admin/subscribelib2.php +++ b/public_html/lists/admin/subscribelib2.php @@ -760,7 +760,7 @@ function ListAvailableLists($userid = 0, $lists_to_show = '') if (isset($GLOBALS['showCat'])&& $GLOBALS['showCat']===true){ $listspercategory = array(); $categories = array(); - $catresult = Sql_query(sprintf('select * from %s %s order by category, listorder, name', + $catresult = Sql_query(sprintf('select * from %s %s order by listorder, name', $GLOBALS['tables']['list'], $subselect)); diff --git a/public_html/lists/admin/upgrade.php b/public_html/lists/admin/upgrade.php index b10e9697b..981f21213 100644 --- a/public_html/lists/admin/upgrade.php +++ b/public_html/lists/admin/upgrade.php @@ -101,16 +101,6 @@ function output($message) Sql_Query(sprintf('delete from %s ',$GLOBALS['tables']['sendprocess'])); ignore_user_abort(1); - // rename tables if we are using the prefix - include dirname(__FILE__).'/structure.php'; - foreach ($DBstruct as $table => $value) { - set_time_limit(500); - if (isset($table_prefix)) { - if (Sql_Table_exists($table) && !Sql_Table_Exists($tables[$table])) { - Sql_Verbose_Query("alter table $table rename $tables[$table]", 1); - } - } - } @ob_end_flush(); @ob_start(); @@ -230,144 +220,12 @@ function output($message) Sql_Query(sprintf('update %s set data = "%s" where data = "%s" ', $GLOBALS['tables']['subscribepage_data'], sql_escape($newFooter), addslashes($value))); } } - //# remember whether we've done this, to avoid doing it every time - //# even thought that's not such a big deal - $isUTF8 = getConfig('UTF8converted'); - - if (empty($isUTF8)) { - $maxsize = 0; - $req = Sql_Query('select (data_length+index_length) tablesize - from information_schema.tables - where table_schema="' .$GLOBALS['database_name'].'"'); - - while ($row = Sql_Fetch_Assoc($req)) { - if ($row['tablesize'] > $maxsize) { - $maxsize = $row['tablesize']; - } - } - $maxsize = (int) ($maxsize * 1.2); //# add another 20% - #this is only valid when the DB is on the same host - if ($GLOBALS['database_host'] == 'localhost') { - $row = Sql_Fetch_Row_Query('select @@datadir'); - $dataDir = $row[0]; - $avail = disk_free_space($dataDir); - } else { - # let's assume the DB host has sufficient space - $avail = $maxsize + 1; - } - - //# convert to UTF8 - $dbname = $GLOBALS['database_name']; - if ($maxsize < $avail && !empty($dbname)) { - //# the conversion complains about a key length - Sql_Query(sprintf('alter table '.$GLOBALS['tables']['user_blacklist_data'].' change column email email varchar(150) not null unique')); - - $req = Sql_Query('select * from information_schema.columns where table_schema = "'.$dbname.'" and CHARACTER_SET_NAME != "utf8"'); - - $dbcolumns = array(); - $dbtables = array(); - while ($row = Sql_Fetch_Assoc($req)) { - //# make sure to only change our own tables, in case we share with other applications - if (in_array($row['TABLE_NAME'], array_values($GLOBALS['tables']))) { - $dbcolumns[] = $row; - $dbtables[$row['TABLE_NAME']] = $row['TABLE_NAME']; - } - } - - Sql_Query('use '.$dbname); - - output($GLOBALS['I18N']->get('Upgrading the database to use UTF-8, please wait').'
'); - foreach ($dbtables as $dbtable) { - set_time_limit(3600); - output($GLOBALS['I18N']->get('Upgrading table ').' '.$dbtable.'
'); - Sql_Query(sprintf('alter table %s default charset utf8', $dbtable), 1); - } - - foreach ($dbcolumns as $dbcolumn) { - set_time_limit(600); - output($GLOBALS['I18N']->get('Upgrading column ').' '.$dbcolumn['COLUMN_NAME'].'
'); - Sql_Query(sprintf('alter table %s change column %s %s %s default character set utf8', - $dbcolumn['TABLE_NAME'], $dbcolumn['COLUMN_NAME'], $dbcolumn['COLUMN_NAME'], - $dbcolumn['COLUMN_TYPE']), 1); - } - output($GLOBALS['I18N']->get('upgrade to UTF-8, done').'
'); - saveConfig('UTF8converted', date('Y-m-d H:i'), 0); - } else { - echo '
'.s('Database requires converting to UTF-8.').'
'; - echo s('However, there is too little diskspace for this conversion').'
'; - echo s('Please do a manual conversion.').' '.PageLinkButton('converttoutf8', - s('Run manual conversion to UTF8')); - echo '
'; - } - } - - //# 2.11.7 and up - Sql_Query(sprintf('alter table %s add column privileges text', $tables['admin']), 1); - Sql_Query('alter table '.$tables['list'].' add column category varchar(255) default ""', 1); - Sql_Query('alter table '.$tables['user_attribute'].' change column value value text'); - Sql_Query('alter table '.$tables['message'].' change column textmessage textmessage longtext'); - Sql_Query('alter table '.$tables['message'].' change column message message longtext'); - Sql_Query('alter table '.$tables['messagedata'].' change column data data longtext'); - Sql_Query('alter table '.$tables['bounce'].' add index statusidx (status(20))', 1); //# fetch the list of TLDs, if possible if (defined('TLD_AUTH_LIST')) { refreshTlds(true); } - //# changed terminology - Sql_Query(sprintf('update %s set status = "invalid email address" where status = "invalid email"', - $tables['usermessage'])); - - //# for some reason there are some config entries marked non-editable, that should be - include_once dirname(__FILE__).'/defaultconfig.php'; - foreach ($default_config as $configItem => $configDetails) { - if (empty($configDetails['hidden'])) { - Sql_Query(sprintf('update %s set editable = 1 where item = "%s"', $tables['config'], $configItem)); - } else { - Sql_Query(sprintf('update %s set editable = 0 where item = "%s"', $tables['config'], $configItem)); - } - } - - //# replace old header and footer with the new one - //# but only if there are untouched from the default, which seems fairly common - $oldPH = @file_get_contents(dirname(__FILE__).'/ui/old_public_header.inc'); - $oldPH2 = preg_replace("/\n/", "\r\n", $oldPH); //# version with \r\n instead of \n - - $oldPF = @file_get_contents(dirname(__FILE__).'/ui/old_public_footer.inc'); - $oldPF2 = preg_replace("/\n/", "\r\n", $oldPF); //# version with \r\n instead of \n - Sql_Query(sprintf('update %s set value = "%s" where item = "pageheader" and (value = "%s" or value = "%s")', - $tables['config'], sql_escape($defaultheader), addslashes($oldPH), addslashes($oldPH2))); - Sql_Query(sprintf('update %s set value = "%s" where item = "pagefooter" and (value = "%s" or value = "%s")', - $tables['config'], sql_escape($defaultfooter), addslashes($oldPF), addslashes($oldPF2))); - - //# and the same for subscribe pages - Sql_Query(sprintf('update %s set data = "%s" where name = "header" and (data = "%s" or data = "%s")', - $tables['subscribepage_data'], sql_escape($defaultheader), addslashes($oldPH), addslashes($oldPH2))); - Sql_Query(sprintf('update %s set data = "%s" where name = "footer" and (data = "%s" or data = "%s")', - $tables['subscribepage_data'], sql_escape($defaultfooter), addslashes($oldPF), addslashes($oldPF2))); - - if (is_file(dirname(__FILE__).'/ui/'.$GLOBALS['ui'].'/old_public_header.inc')) { - $oldPH = file_get_contents(dirname(__FILE__).'/ui/'.$GLOBALS['ui'].'/old_public_header.inc'); - $oldPH2 = preg_replace("/\n/", "\r\n", $oldPH); //# version with \r\n instead of \n - $oldPF = file_get_contents(dirname(__FILE__).'/ui/'.$GLOBALS['ui'].'/old_public_footer.inc'); - $oldPF2 = preg_replace("/\n/", "\r\n", $oldPF); //# version with \r\n instead of \n - $currentPH = getConfig('pageheader'); - $currentPF = getConfig('pagefooter'); - - if (($currentPH == $oldPH2 || $currentPH."\r\n" == $oldPH2) && !empty($defaultheader)) { - SaveConfig('pageheader', $defaultheader, 1); - Sql_Query(sprintf('update %s set data = "%s" where name = "header" and data = "%s"', - $tables['subscribepage_data'], sql_escape($defaultheader), addslashes($currentPH))); - //# only try to change footer when header has changed - if ($currentPF == $oldPF2 && !empty($defaultfooter)) { - SaveConfig('pagefooter', $defaultfooter, 1); - Sql_Query(sprintf('update %s set data = "%s" where name = "footer" and data = "%s"', - $tables['subscribepage_data'], sql_escape($defaultfooter), addslashes($currentPF))); - } - } - } - //# #17328 - remove list categories with quotes Sql_Query(sprintf("update %s set category = replace(category,\"\\\\'\",\" \")", $tables['list'])); @@ -402,7 +260,7 @@ function output($message) // output(s('Giving a UUID to your subscribers and campaigns. If you have a lot of them, this may take a while.')); // output(s('If the page times out, you can reload. Or otherwise try to run the upgrade from commandline instead.').' '.resourceLink('https://resources.phplist.com/system/commandline', s('Documentation how to set up phpList commandline'))); - } else { + } elseif ($numS > 0) { output(s('Giving a UUID to your subscribers and campaigns. If you have a lot of them, this may take a while.')); output(s('If the page times out, you can reload. Or otherwise try to run the upgrade from commandline instead.').' '.resourceLink('https://resources.phplist.com/system/commandline', s('Documentation how to set up phpList commandline'))); while ($row = Sql_Fetch_Row($req)) { @@ -468,6 +326,50 @@ function output($message) Sql_Query("alter table {$GLOBALS['tables']['admin']} modify modifiedby varchar(66) default ''"); } + if (version_compare($dbversion, '3.6.15','<')) { + // support utf8mb4 for campaign subject and content + Sql_Query("alter table {$GLOBALS['tables']['message']} modify subject varchar(255) character set utf8mb4 not null default '(no subject)'"); + Sql_Query("alter table {$GLOBALS['tables']['message']} modify message longtext character set utf8mb4"); + Sql_Query("alter table {$GLOBALS['tables']['message']} modify textmessage longtext character set utf8mb4"); + Sql_Query("alter table {$GLOBALS['tables']['messagedata']} modify data longtext character set utf8mb4"); + } + + if (!Sql_Table_exists($GLOBALS['tables']['admin_login'])) { + cl_output(s('Creating new table "admin_login"')); + createTable('admin_login'); + ## add an entry for current admin to avoid being kicked out + Sql_Query(sprintf('insert into %s (moment,adminid,remote_ip4,remote_ip6,sessionid,active) + values(%d,%d,"%s","%s","%s",1)', + $GLOBALS['tables']['admin_login'],time(),$_SESSION['logindetails']['id'],$_SESSION['adminloggedin'],"",session_id())); + } + if (version_compare($dbversion, '3.6.15', '<')) { + // Ensure timestamp field does not have null values then give explicit defaults + Sql_Query(sprintf('update %s set modified = created where modified is null', $GLOBALS['tables']['admin'])); + Sql_Query(sprintf('update %s set modified = entered where modified is null', $GLOBALS['tables']['list'])); + Sql_Query(sprintf('update %s set modified = entered where modified is null', $GLOBALS['tables']['listmessage'])); + Sql_Query(sprintf('update %s set modified = entered where modified is null', $GLOBALS['tables']['listuser'])); + Sql_Query(sprintf('update %s set modified = entered where modified is null', $GLOBALS['tables']['message'])); + Sql_Query(sprintf('update %s set modified = started where modified is null', $GLOBALS['tables']['sendprocess'])); + Sql_Query(sprintf('update %s set modified = entered where modified is null', $GLOBALS['tables']['user'])); + Sql_Query(sprintf('update %s set time = current_timestamp where time is null', $GLOBALS['tables']['user_message_bounce'])); + Sql_Query(sprintf('update %s set time = current_timestamp where time is null', $GLOBALS['tables']['user_message_forward'])); + + foreach (['admin', 'list', 'listmessage' , 'listuser', 'message', 'sendprocess', 'user'] as $t) { + Sql_Query(sprintf( + 'alter table %s modify modified timestamp not null default current_timestamp on update current_timestamp', + $GLOBALS['tables'][$t] + )); + } + Sql_Query(sprintf( + 'alter table %s modify time timestamp not null default current_timestamp on update current_timestamp', + $GLOBALS['tables']['user_message_bounce'] + )); + Sql_Query(sprintf( + 'alter table %s modify time timestamp not null default current_timestamp on update current_timestamp', + $GLOBALS['tables']['user_message_forward'] + )); + } + //# longblobs are better at mixing character encoding. We don't know the encoding of anything we may want to store in cache //# before converting, it's quickest to clear the cache clearPageCache(); diff --git a/public_html/lists/admin/user.php b/public_html/lists/admin/user.php index d5f800f94..421cc64fb 100644 --- a/public_html/lists/admin/user.php +++ b/public_html/lists/admin/user.php @@ -465,7 +465,7 @@ class="confirm btn btn-default" while ($row = Sql_fetch_array($res)) { if (!empty($id)) { $val_req = Sql_Fetch_Row_Query("select value from $tables[user_attribute] where userid = $id and attributeid = $row[id]"); - $row['value'] = $val_req[0]; + $row['value'] = !empty($val_req[0]) ? $val_req[0] : ""; } elseif (!empty($_POST['attribute'][$row['id']])) { $row['value'] = $_POST['attribute'][$row['id']]; } else { diff --git a/public_html/lists/admin/users.php b/public_html/lists/admin/users.php index d27dbe89e..635f53a39 100644 --- a/public_html/lists/admin/users.php +++ b/public_html/lists/admin/users.php @@ -45,6 +45,8 @@ } $unconfirmed = !empty($_GET['unconfirmed']) ? sprintf('%d', $_GET['unconfirmed']) : 0; $blacklisted = !empty($_GET['blacklisted']) ? sprintf('%d', $_GET['blacklisted']) : 0; +$confirmed = !empty($_GET['confirmed']) ? sprintf('%d', $_GET['confirmed']) : 0; +$nonblacklisted = !empty($_GET['nonblacklisted']) ? sprintf('%d', $_GET['nonblacklisted']) : 0; if (isset($_GET['sortorder'])) { if ($_GET['sortorder'] == 'asc') { $sortorder = 'asc'; @@ -147,10 +149,15 @@ $subselect = "{$tables['user']}.id = {$tables['listuser']}.userid and {$tables['listuser']}.listid = {$tables['list']}.id and {$tables['list']}.owner = ".$_SESSION['logindetails']['id']; if ($unconfirmed) { $subselect .= ' and !confirmed '; + } elseif ($confirmed) { + $subselect .= ' and confirmed '; } if ($blacklisted) { $subselect .= ' and blacklisted '; + } elseif ($nonblacklisted) { + $subselect .= ' and !blacklisted '; } + if ($find && $findbyselect) { $listquery = "select DISTINCT {$tables['user']}.email,{$tables['user']}.id,$findfield,confirmed from ".$table_list." where $subselect and $findbyselect"; $count = Sql_query("SELECT count(distinct {$tables['user']}.id) FROM ".$table_list." where $subselect and $findbyselect"); @@ -167,9 +174,13 @@ if ($find && $findbyselect) { if ($unconfirmed) { $findbyselect .= ' and !confirmed '; + } elseif ($confirmed) { + $findbyselect .= ' and confirmed '; } if ($blacklisted) { $findbyselect .= ' and blacklisted '; + } elseif ($nonblacklisted) { + $findbyselect .= ' and !blacklisted '; } $listquery = "select DISTINCT {$tables['user']}.email,{$tables['user']}.id,$findfield,{$tables['user']}.confirmed from ".$table_list." where $findbyselect"; $count = Sql_query('SELECT count(*) FROM '.$table_list." where $findbyselect"); @@ -195,21 +206,35 @@ if ($find && $findbyselect) { if ($unconfirmed) { $findbyselect .= ' and !confirmed '; + } elseif ($confirmed) { + $findbyselect .= ' and confirmed '; } if ($blacklisted) { $findbyselect .= ' and blacklisted '; + } elseif ($nonblacklisted) { + $findbyselect .= ' and !blacklisted '; } $listquery = "select {$tables['user']}.email,{$tables['user']}.id,$findfield,{$tables['user']}.confirmed from ".$table_list." where $findbyselect"; $count = Sql_query('SELECT count(*) FROM '.$table_list." where $findbyselect"); $unconfirmedcount = Sql_query('SELECT count(*) FROM '.$table_list." where !confirmed and $findbyselect"); } else { $subselect = ''; - if ($unconfirmed || $blacklisted) { + if ($unconfirmed || $blacklisted || $confirmed || $nonblacklisted) { $subselect = ' where '; if ($unconfirmed && $blacklisted) { $subselect .= ' !confirmed and blacklisted '; + } elseif ($confirmed && $nonblacklisted) { + $subselect .= ' confirmed and !blacklisted '; + } elseif ($unconfirmed && $nonblacklisted) { + $subselect .= ' !confirmed and !blacklisted '; + } elseif ($confirmed && $blacklisted) { + $subselect .= ' confirmed and blacklisted '; } elseif ($unconfirmed) { $subselect .= ' !confirmed '; + } elseif ($confirmed) { + $subselect .= ' confirmed '; + } elseif ($nonblacklisted) { + $subselect .= ' !blacklisted '; } else { $subselect .= ' blacklisted'; } @@ -280,6 +305,16 @@ } else { $bll = ''; } +if ($confirmed) { + $nonunc = 'checked="checked"'; +} else { + $nonunc = ''; +} +if ($nonblacklisted) { + $nonbll = 'checked="checked"'; +} else { + $nonbll = ''; +} if (!isset($start)) { $start = 0; } @@ -290,15 +325,22 @@ - - ', + + +
+ + ', $start, htmlspecialchars(stripslashes($find)), htmlspecialchars(stripslashes($findby)), - $GLOBALS['I18N']->get('Show only unconfirmed users'), + $GLOBALS['I18N']->get('Show only unconfirmed subscribers'), $unc, - $GLOBALS['I18N']->get('Show only blacklisted users'), - $bll); + $GLOBALS['I18N']->get('Show only blacklisted subscribers'), + $bll, + $GLOBALS['I18N']->get('Show only confirmed subscribers'), + $nonunc, + $GLOBALS['I18N']->get('Show only non blacklisted subscribers'), + $nonbll); //print '
'.$GLOBALS['I18N']->get('For subscriber with email').'
'.s('For subscriber with email').$furtherDetails.'
'.$GLOBALS['I18N']->get('Increase bouncecount with').'
'.$GLOBALS['I18N']->get('(use negative numbers to decrease)').'
'.$GLOBALS['I18N']->get('Mark subscriber as unconfirmed').'
'.$GLOBALS['I18N']->get('(so you can resend the request for confirmation)').'
'.s('Mark subscriber as unconfirmed').'
'.s('(so you can resend the request for confirmation)').'
'.s('or confirmed').'
'.s('(so you can revert possible auto unconfirmation)'). '
'.s('unconfirmed').''.s('confirmed').'
'.$GLOBALS['I18N']->get('Set subscriber to receive text instead of HTML').'
'.$GLOBALS['I18N']->get('Delete subscriber').'
'; $select = ''; foreach (array( diff --git a/public_html/lists/index.php b/public_html/lists/index.php index 10b00fd05..cc6277a1b 100644 --- a/public_html/lists/index.php +++ b/public_html/lists/index.php @@ -1145,8 +1145,8 @@ function forwardPage($id) sendAdminCopy(s('Message Forwarded'), s('%s has forwarded message %d to %s', $userdata['email'], $mid, $email), $messagelists); - Sql_Query(sprintf('insert into %s (user,message,forward,status,time) - values(%d,%d,"%s","sent",now())', + Sql_Query(sprintf('insert into %s (user,message,forward,status) + values(%d,%d,"%s","sent")', $tables['user_message_forward'], $userdata['id'], $mid, $email)); if ($iCountFriends) { ++$nFriends; @@ -1156,8 +1156,8 @@ function forwardPage($id) sendAdminCopy(s('Message Forwarded'), s('%s tried forwarding message %d to %s but failed', $userdata['email'], $mid, $email), $messagelists); - Sql_Query(sprintf('insert into %s (user,message,forward,status,time) - values(%d,%d,"%s","failed",now())', + Sql_Query(sprintf('insert into %s (user,message,forward,status) + values(%d,%d,"%s","failed")', $tables['user_message_forward'], $userdata['id'], $mid, $email)); $ok = false; } diff --git a/tests/features/getting-started/updateTranslations.feature b/tests/features/getting-started/updateTranslations.feature index 2ee6bc83a..080cd5119 100644 --- a/tests/features/getting-started/updateTranslations.feature +++ b/tests/features/getting-started/updateTranslations.feature @@ -1,4 +1,4 @@ -@javascript +@javascript @wip Feature: Get new translated strings In order to get available translated application strings As an admin user