1<?php 2 3require_once dirname(__FILE__).'/accesscheck.php'; 4 5if (!$GLOBALS['commandline']) { 6 @ob_end_flush(); 7} else { 8 //# when on cl, doit immediately 9 $_GET['doit'] = 'yes'; 10} 11$force = isset($cline['f']) || isset($_GET['force']); 12 13function output($message) 14{ 15 if ($GLOBALS['commandline']) { 16 @ob_end_clean(); 17 echo strip_tags($message)."\n"; 18 ob_start(); 19 } else { 20 echo $message; 21 // output some stuff to make sure it's not buffered in the browser, hmm, would be nice to find a better way for this 22 for ($i = 0; $i < 10000; ++$i) { 23 echo ' '."\n"; 24 } 25 flush(); 26 @ob_end_flush(); 27 } 28 flush(); 29} 30 31output(""); 32$dbversion = getConfig('version'); 33$releaseDBversion = getConfig('releaseDBversion'); // release version check 34$inUpgrade = getConfig('in-upgrade-to'); 35if (!empty($inUpgrade) && $inUpgrade == VERSION) { 36 if (!$force) { 37 if ($GLOBALS['commandline']) { 38 output(s('Another process is already upgrading this installation. Use -f to force upgrade, but only if you are sure the other process is no longer active.')); 39 } else { 40 output(s('Another process is already upgrading this installation. Click %s to force upgrade, but only if you are sure the other process is no longer active.', PageLinkButton('upgrade&doit=yes&force=1', s('Force Upgrade')))); 41 } 42 43 return; 44 } 45} 46 47if (!$dbversion) { 48 $dbversion = 'Older than 1.4.1'; 49} 50output('<p class="information">'.$GLOBALS['I18N']->get('Your database version').': '.$dbversion.'</p>'); 51 52if ($GLOBALS['database_module'] == 'mysql.inc') { 53 echo Warn(s('Please edit your config file and change "mysql.inc" to "mysqli.inc" to avoid future PHP incompatibility'). 54 resourceLink('http://resources.phplist.com/system/mysql-mysqli-update') 55 ); 56} 57 58if (!empty($GLOBALS['mysql_database_engine'])) { 59 $engines_count = Sql_Fetch_Row_Query(sprintf('select count(table_name) from information_schema.tables where engine != \'%s\' and table_schema = \'%s\'',$GLOBALS['mysql_database_engine'],$GLOBALS['database_name'])); 60 if (!empty($engines_count[0])) { 61 if ($GLOBALS['commandline']) { 62 cl_output(s('Converting tables to preferred database engine')); 63 $engines = Sql_Query(sprintf('select table_name from information_schema.tables where engine != \'%s\' and table_schema = \'%s\'',$GLOBALS['mysql_database_engine'],$GLOBALS['database_name'])); 64 while ($engine = Sql_Fetch_Assoc($engines)) { 65 cl_output(s('Converting table %s',$engine['table_name'])); 66 Sql_Query(sprintf('alter table %s Engine %s',$engine['table_name'],$GLOBALS['mysql_database_engine'])); 67 } 68 } else { 69 echo Warn(s('You have %d tables that do not use your preferred database engine',$engines_count[0]).'<br/>'.s('Use the commandline upgrade method to convert them')); 70 } 71 } 72} 73 74if (!versionCompare($dbversion,'2.11.11') && $dbversion!=='dev') { 75 Fatal_Error(s('Your version is older than 3.2.0 and cannot be upgraded to this version. Please upgrade to 3.2.0 first and then try again.')); 76 return; 77} 78 79// only action upgrade if necessary 80if ($force && $dbversion == VERSION && defined('RELEASEDATE') && RELEASEDATE <= $releaseDBversion) { 81 output(s('Your database is already the correct version (%s), including release date version (%s), there is no need to upgrade',$dbversion, $releaseDBversion)); 82 clearMaintenanceMode(); 83 unset($_GET['doit']); 84} 85 86if ($dbversion == VERSION && !$force) { 87 output($GLOBALS['I18N']->get('Your database is already the correct version, there is no need to upgrade')); 88 89 echo '<p>'.PageLinkAjax('upgrade&update=tlds', s('update Top Level Domains'), '', 'button').'</p>'; 90 clearMaintenanceMode(); 91 92 echo subscribeToAnnouncementsForm(); 93} elseif (isset($_GET['doit']) && $_GET['doit'] == 'yes') { 94 $success = 1; 95 // once we are off, this should not be interrupted 96 97 // mark the system to be in maintenance mode 98 setMaintenanceMode(s('Upgrading phpList to version '.VERSION)); 99 100 // force send and other processes to stop 101 Sql_Query(sprintf('delete from %s ',$GLOBALS['tables']['sendprocess'])); 102 103 ignore_user_abort(1); 104 // rename tables if we are using the prefix 105 include dirname(__FILE__).'/structure.php'; 106 foreach ($DBstruct as $table => $value) { 107 set_time_limit(500); 108 if (isset($table_prefix)) { 109 if (Sql_Table_exists($table) && !Sql_Table_Exists($tables[$table])) { 110 Sql_Verbose_Query("alter table $table rename $tables[$table]", 1); 111 } 112 } 113 } 114 @ob_end_flush(); 115 @ob_start(); 116 117 output('<p class="information">'.$GLOBALS['I18N']->get('Please wait, upgrading your database, do not interrupt').'</p>'); 118 119 flush(); 120 121 if (preg_match('/(.*?)-/', $dbversion, $regs)) { 122 $dbversion = $regs[1]; 123 } 124 125 //# lock this process 126 SaveConfig('in-upgrade-to', VERSION, 1); 127 128 if (version_compare($dbversion, '3.3.4','<')) { 129 130 Sql_Query("alter table {$GLOBALS['tables']['bounce']} modify data mediumblob "); 131 132 $indexesToRecreate = array( 133 'urlcache' => array( 134 'urlindex' => array('value' => 'url(255)', 'unique' => false), 135 ), 136 'linktrack_forward' => array( 137 'urlindex' => array('value' => 'url(255)', 'unique' => false), 138 'urlunique' => array('value' => 'urlhash', 'unique' => true), 139 ), 140 'bounceregex' => array( 141 'regex' => array('value' => 'regexhash', 'unique'=> true), 142 ), 143 ); 144 145 $tablesToAlter = array( 146 'urlcache' => array('url'), 147 'linktrack_forward' => array('url'), 148 'bounceregex' => array('regex'), 149 ); 150 151 //add columns for hash values 152 153 Sql_Query("alter table {$GLOBALS['tables']['linktrack_forward']} add urlhash char(32) "); 154 Sql_Query("alter table {$GLOBALS['tables']['bounceregex']} add regexhash char(32) "); 155 156 // add hash values 157 158 Sql_Query("update {$GLOBALS['tables']['linktrack_forward']} set urlhash = md5(url) where urlhash is NULL "); 159 Sql_Query("update {$GLOBALS['tables']['bounceregex']} set regexhash = md5(regex) where regexhash is NULL "); 160 161 162 163 foreach($indexesToRecreate as $table => $indexes) { 164 165 166 foreach($indexes as $indexName => $settings) { 167 168 $exists = table_index_exists($GLOBALS['tables'][$table],$indexName); 169 if ($exists) { 170 Sql_Query("drop index $indexName on {$GLOBALS['tables'][$table]} "); 171 } 172 } 173 174 $alteringOperations = $tablesToAlter[$table]; 175 foreach($alteringOperations as $operation) { 176 Sql_Query("alter table {$GLOBALS['tables'][$table]} modify $operation varchar(2083) "); 177 } 178 179 foreach($indexes as $indexName => $settings) { 180 $createStmt = ''; 181 if($settings['unique'] === true) { 182 $createStmt = 'create unique index'; 183 } else { 184 $createStmt = 'create index'; 185 } 186 187 Sql_Query("$createStmt $indexName on {$GLOBALS['tables'][$table]}({$settings['value']})"); 188 } 189 190 } 191 } 192 193 // Update jQuery version referenced in public page HTML stored in the database 194 if (version_compare($dbversion, '3.4.1', '<')) { 195 196 // The new filename does not hard-code the jQuery version number 197 $replacement = "jquery.min.js"; 198 199 // Replace jQuery version public page footers in config table 200 $oldConfigFooter = getConfig('pagefooter'); 201 $matches = null; 202 203 // Find and replace all references to version-specific jQuery files 204 preg_match('/jquery-3.3.1.min.js/', $oldConfigFooter, $matches); 205 if ($matches[0] == "jquery-3.3.1.min.js") { 206 $pattern = "jquery-3.3.1.min.js"; 207 } else { 208 $pattern = "jquery-1.12.1.min.js"; 209 } 210 211 $newConfigFooter = str_replace($pattern, $replacement, $oldConfigFooter); 212 SaveConfig('pagefooter', $newConfigFooter); 213 214 //Replace jQuery version for each subscribe page data. 215 $req = Sql_Query(sprintf('select data from %s where name = "footer"', $GLOBALS['tables']['subscribepage_data'])); 216 $footersArray = array(); 217 while ($row = Sql_Fetch_Assoc($req)) { 218 $footersArray[] = $row['data']; 219 } 220 221 // Find and replace all references to version-specific jQuery files 222 foreach ($footersArray as $key => $value) { 223 preg_match('/jquery-3.3.1.min.js/', $value, $matches); 224 if ($matches[0] == "jquery-3.3.1.min.js") { 225 $pattern = "jquery-3.3.1.min.js"; 226 } else { 227 $pattern = "jquery-1.12.1.min.js"; 228 } 229 $newFooter = str_replace($pattern, $replacement, $value); 230 Sql_Query(sprintf('update %s set data = "%s" where data = "%s" ', $GLOBALS['tables']['subscribepage_data'], sql_escape($newFooter), addslashes($value))); 231 } 232 } 233 //# remember whether we've done this, to avoid doing it every time 234 //# even thought that's not such a big deal 235 $isUTF8 = getConfig('UTF8converted'); 236 237 if (empty($isUTF8)) { 238 $maxsize = 0; 239 $req = Sql_Query('select (data_length+index_length) tablesize 240 from information_schema.tables 241 where table_schema="' .$GLOBALS['database_name'].'"'); 242 243 while ($row = Sql_Fetch_Assoc($req)) { 244 if ($row['tablesize'] > $maxsize) { 245 $maxsize = $row['tablesize']; 246 } 247 } 248 $maxsize = (int) ($maxsize * 1.2); //# add another 20% 249 $row = Sql_Fetch_Row_Query('select @@datadir'); 250 $dataDir = $row[0]; 251 $avail = disk_free_space($dataDir); 252 253 //# convert to UTF8 254 $dbname = $GLOBALS['database_name']; 255 if ($maxsize < $avail && !empty($dbname)) { 256 //# the conversion complains about a key length 257 Sql_Query(sprintf('alter table '.$GLOBALS['tables']['user_blacklist_data'].' change column email email varchar(150) not null unique')); 258 259 $req = Sql_Query('select * from information_schema.columns where table_schema = "'.$dbname.'" and CHARACTER_SET_NAME != "utf8"'); 260 261 $dbcolumns = array(); 262 $dbtables = array(); 263 while ($row = Sql_Fetch_Assoc($req)) { 264 //# make sure to only change our own tables, in case we share with other applications 265 if (in_array($row['TABLE_NAME'], array_values($GLOBALS['tables']))) { 266 $dbcolumns[] = $row; 267 $dbtables[$row['TABLE_NAME']] = $row['TABLE_NAME']; 268 } 269 } 270 271 Sql_Query('use '.$dbname); 272 273 output($GLOBALS['I18N']->get('Upgrading the database to use UTF-8, please wait').'<br/>'); 274 foreach ($dbtables as $dbtable) { 275 set_time_limit(3600); 276 output($GLOBALS['I18N']->get('Upgrading table ').' '.$dbtable.'<br/>'); 277 Sql_Query(sprintf('alter table %s default charset utf8', $dbtable), 1); 278 } 279 280 foreach ($dbcolumns as $dbcolumn) { 281 set_time_limit(600); 282 output($GLOBALS['I18N']->get('Upgrading column ').' '.$dbcolumn['COLUMN_NAME'].'<br/>'); 283 Sql_Query(sprintf('alter table %s change column %s %s %s default character set utf8', 284 $dbcolumn['TABLE_NAME'], $dbcolumn['COLUMN_NAME'], $dbcolumn['COLUMN_NAME'], 285 $dbcolumn['COLUMN_TYPE']), 1); 286 } 287 output($GLOBALS['I18N']->get('upgrade to UTF-8, done').'<br/>'); 288 saveConfig('UTF8converted', date('Y-m-d H:i'), 0); 289 } else { 290 echo '<div class="error">'.s('Database requires converting to UTF-8.').'<br/>'; 291 echo s('However, there is too little diskspace for this conversion').'<br/>'; 292 echo s('Please do a manual conversion.').' '.PageLinkButton('converttoutf8', 293 s('Run manual conversion to UTF8')); 294 echo '</div>'; 295 } 296 } 297 298 //# 2.11.7 and up 299 Sql_Query(sprintf('alter table %s add column privileges text', $tables['admin']), 1); 300 Sql_Query('alter table '.$tables['list'].' add column category varchar(255) default ""', 1); 301 Sql_Query('alter table '.$tables['user_attribute'].' change column value value text'); 302 Sql_Query('alter table '.$tables['message'].' change column textmessage textmessage longtext'); 303 Sql_Query('alter table '.$tables['message'].' change column message message longtext'); 304 Sql_Query('alter table '.$tables['messagedata'].' change column data data longtext'); 305 Sql_Query('alter table '.$tables['bounce'].' add index statusidx (status(20))', 1); 306 307 //# fetch the list of TLDs, if possible 308 if (defined('TLD_AUTH_LIST')) { 309 refreshTlds(true); 310 } 311 312 //# changed terminology 313 Sql_Query(sprintf('update %s set status = "invalid email address" where status = "invalid email"', 314 $tables['usermessage'])); 315 316 //# for some reason there are some config entries marked non-editable, that should be 317 include_once dirname(__FILE__).'/defaultconfig.php'; 318 foreach ($default_config as $configItem => $configDetails) { 319 if (empty($configDetails['hidden'])) { 320 Sql_Query(sprintf('update %s set editable = 1 where item = "%s"', $tables['config'], $configItem)); 321 } else { 322 Sql_Query(sprintf('update %s set editable = 0 where item = "%s"', $tables['config'], $configItem)); 323 } 324 } 325 326 //# replace old header and footer with the new one 327 //# but only if there are untouched from the default, which seems fairly common 328 $oldPH = @file_get_contents(dirname(__FILE__).'/ui/old_public_header.inc'); 329 $oldPH2 = preg_replace("/\n/", "\r\n", $oldPH); //# version with \r\n instead of \n 330 331 $oldPF = @file_get_contents(dirname(__FILE__).'/ui/old_public_footer.inc'); 332 $oldPF2 = preg_replace("/\n/", "\r\n", $oldPF); //# version with \r\n instead of \n 333 Sql_Query(sprintf('update %s set value = "%s" where item = "pageheader" and (value = "%s" or value = "%s")', 334 $tables['config'], sql_escape($defaultheader), addslashes($oldPH), addslashes($oldPH2))); 335 Sql_Query(sprintf('update %s set value = "%s" where item = "pagefooter" and (value = "%s" or value = "%s")', 336 $tables['config'], sql_escape($defaultfooter), addslashes($oldPF), addslashes($oldPF2))); 337 338 //# and the same for subscribe pages 339 Sql_Query(sprintf('update %s set data = "%s" where name = "header" and (data = "%s" or data = "%s")', 340 $tables['subscribepage_data'], sql_escape($defaultheader), addslashes($oldPH), addslashes($oldPH2))); 341 Sql_Query(sprintf('update %s set data = "%s" where name = "footer" and (data = "%s" or data = "%s")', 342 $tables['subscribepage_data'], sql_escape($defaultfooter), addslashes($oldPF), addslashes($oldPF2))); 343 344 if (is_file(dirname(__FILE__).'/ui/'.$GLOBALS['ui'].'/old_public_header.inc')) { 345 $oldPH = file_get_contents(dirname(__FILE__).'/ui/'.$GLOBALS['ui'].'/old_public_header.inc'); 346 $oldPH2 = preg_replace("/\n/", "\r\n", $oldPH); //# version with \r\n instead of \n 347 $oldPF = file_get_contents(dirname(__FILE__).'/ui/'.$GLOBALS['ui'].'/old_public_footer.inc'); 348 $oldPF2 = preg_replace("/\n/", "\r\n", $oldPF); //# version with \r\n instead of \n 349 $currentPH = getConfig('pageheader'); 350 $currentPF = getConfig('pagefooter'); 351 352 if (($currentPH == $oldPH2 || $currentPH."\r\n" == $oldPH2) && !empty($defaultheader)) { 353 SaveConfig('pageheader', $defaultheader, 1); 354 Sql_Query(sprintf('update %s set data = "%s" where name = "header" and data = "%s"', 355 $tables['subscribepage_data'], sql_escape($defaultheader), addslashes($currentPH))); 356 //# only try to change footer when header has changed 357 if ($currentPF == $oldPF2 && !empty($defaultfooter)) { 358 SaveConfig('pagefooter', $defaultfooter, 1); 359 Sql_Query(sprintf('update %s set data = "%s" where name = "footer" and data = "%s"', 360 $tables['subscribepage_data'], sql_escape($defaultfooter), addslashes($currentPF))); 361 } 362 } 363 } 364 365 //# #17328 - remove list categories with quotes 366 Sql_Query(sprintf("update %s set category = replace(category,\"\\\\'\",\" \")", $tables['list'])); 367 368 //# add uuid columns 369 if (!Sql_Table_Column_Exists($GLOBALS['tables']['message'], 'uuid')) { 370 Sql_Query(sprintf('alter table %s add column uuid varchar(36) default ""', 371 $GLOBALS['tables']['message'])); 372 Sql_Query(sprintf('alter table %s add index uuididx (uuid)', 373 $GLOBALS['tables']['message'])); 374 } 375 if (!Sql_Table_Column_Exists($GLOBALS['tables']['linktrack_forward'], 'uuid')) { 376 Sql_Query(sprintf('alter table %s add column uuid varchar(36) default ""', 377 $GLOBALS['tables']['linktrack_forward'])); 378 Sql_Query(sprintf('alter table %s add index uuididx (uuid)', 379 $GLOBALS['tables']['linktrack_forward'])); 380 } 381 if (!Sql_Table_Column_Exists($GLOBALS['tables']['user'], 'uuid')) { 382 Sql_Query(sprintf('alter table %s add column uuid varchar(36) default ""', 383 $GLOBALS['tables']['user'])); 384 Sql_Query(sprintf('alter table %s add index uuididx (uuid)', 385 $GLOBALS['tables']['user'])); 386 } 387 // add uuids to those that do not have it 388 $req = Sql_Query(sprintf('select id from %s where uuid = ""', $GLOBALS['tables']['user'])); 389 $numS = Sql_Affected_Rows(); 390 if ($numS > 500 && empty($GLOBALS['commandline'])) { 391 392 // with a lot of subscrirbers this can take a very long time, causing a blank page for a long time (I had one system where it took almost an hour) 393 //.This really needs to be loaded in Async mode, therefore I'm removing this for now 394 // it is not strictly necessary to do this here, because processqueue does it as well. 395 // that does mean that the first process queue may take a while. 396 397 // output(s('Giving a UUID to your subscribers and campaigns. If you have a lot of them, this may take a while.')); 398 // 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'))); 399 } else { 400 output(s('Giving a UUID to your subscribers and campaigns. If you have a lot of them, this may take a while.')); 401 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'))); 402 while ($row = Sql_Fetch_Row($req)) { 403 Sql_Query(sprintf('update %s set uuid = "%s" where id = %d', $GLOBALS['tables']['user'], (string)uuid::generate(4), $row[0])); 404 } 405 } 406 407 // let's hope there aren't too many campaigns or links, otherwise the same timeout would apply. 408 409 $req = Sql_Query(sprintf('select id from %s where uuid = ""', $GLOBALS['tables']['message'])); 410 while ($row = Sql_Fetch_Row($req)) { 411 Sql_Query(sprintf('update %s set uuid = "%s" where id = %d', $GLOBALS['tables']['message'], (string) uuid::generate(4), $row[0])); 412 } 413 $req = Sql_Query(sprintf('select id from %s where uuid = ""', $GLOBALS['tables']['linktrack_forward'])); 414 while ($row = Sql_Fetch_Row($req)) { 415 Sql_Query(sprintf('update %s set uuid = "%s" where id = %d', $GLOBALS['tables']['linktrack_forward'], (string) uuid::generate(4), $row[0])); 416 } 417 418 if (!Sql_Table_Exists($tables['admin_password_request'])) { 419 createTable('admin_password_request'); 420 } 421 422 if (!Sql_Table_exists($GLOBALS['tables']['user_message_view'])) { 423 cl_output(s('Creating new table "user_message_view"')); 424 createTable('user_message_view'); 425 } 426 427 if (version_compare($dbversion, '3.3.3','<')) { 428 // add a draft campaign for invite plugin 429 addInviteCampaign(); 430 } 431 432 if (version_compare($dbversion, '3.3.4','<')) { 433 Sql_Query("alter table {$GLOBALS['tables']['bounce']} modify data mediumblob "); 434 } 435 436 if (version_compare($dbversion, '3.4.0-RC1','<')) { 437 SaveConfig('secret', bin2hex(random_bytes(20))); 438 } 439 440 if (version_compare($dbversion, '3.6.0','<')) { 441 Sql_Query("alter table {$GLOBALS['tables']['message']} change column processed processed integer "); 442 } 443 444 if (!Sql_Table_Column_Exists($GLOBALS['tables']['template'], 'template_text')) { 445 Sql_Query(sprintf('alter table %s add column template_text longblob after template', 446 $GLOBALS['tables']['template'])); 447 //# no change in behavior for existing templates 448 Sql_Query(sprintf('update %s set template_text="[CONTENT]"', 449 $GLOBALS['tables']['template'])); 450 } 451 452 //# longblobs are better at mixing character encoding. We don't know the encoding of anything we may want to store in cache 453 //# before converting, it's quickest to clear the cache 454 clearPageCache(); 455 Sql_Query(sprintf('alter table %s change column content content longblob', $tables['urlcache'])); 456 457 //# unlock the upgrade process 458 Sql_Query(sprintf('delete from %s where item = "in-upgrade-to"', $tables['config'])); 459 // mark the database to be our current version 460 if ($success) { 461 SaveConfig('version', VERSION, 0); 462 if (defined('RELEASEDATE')) { 463 SaveConfig('releaseDBversion', RELEASEDATE, 0); 464 } 465 // mark now to be the last time we checked for an update 466 SaveConfig('lastcheckupdate', date('m/d/Y h:i:s', time()), 0, true); 467 //# also clear any possible value for "updateavailable" 468 Sql_Query(sprintf('delete from %s where item = "updateavailable"', $tables['config'])); 469 470 Info(s('Success'), 1); 471 472 upgradePlugins(array_keys($GLOBALS['plugins'])); 473 474 echo subscribeToAnnouncementsForm(); 475 476//# check for old click track data 477 $num = Sql_Fetch_Row_Query(sprintf('select count(*) from %s', $GLOBALS['tables']['linktrack'])); 478 if ($num[0] > 0) { 479 echo '<p class="information">'.$GLOBALS['I18N']->get('The clicktracking system has changed').'</p>'; 480 printf($GLOBALS['I18N']->get('You have %s entries in the old statistics table'), $num[0]).' '; 481 echo ' '.PageLinkButton('convertstats', $GLOBALS['I18N']->get('Convert Old data to new')); 482 } 483 484 if ($GLOBALS['commandline']) { 485 output($GLOBALS['I18N']->get('Upgrade successful')); 486 } 487 } else { 488 Error('An error occurred while upgrading your database'); 489 if ($GLOBALS['commandline']) { 490 output($GLOBALS['I18N']->get('Upgrade failed')); 491 } 492 } 493 clearMaintenanceMode(); 494} else { 495 echo '<p>'.s('Your database requires upgrading, please make sure to create a backup of your database first.').'</p>'; 496 echo '<p>'.s('If you have a large database, make sure you have sufficient diskspace available for upgrade.').'</p>'; 497 echo '<p>'.s('When you are ready click %s Depending on the size of your database, this may take quite a while. Please make sure not to interrupt the process, once it started.', 498 PageLinkButton('upgrade&doit=yes', s('Upgrade'))).'</p>'; 499} 500