2 rcs_id('$Id: upgrade.php,v 1.22 2004-07-03 17:21:28 rurban Exp $');
5 Copyright 2004 $ThePhpWikiProgrammingTeam
7 This file is part of PhpWiki.
9 PhpWiki is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 PhpWiki is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with PhpWiki; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 * Upgrade the WikiDB and config settings after installing a new
28 * Status: experimental, no queries for verification yet, no db update,
31 * Installation on an existing PhpWiki database needs some
32 * additional worksteps. Each step will require multiple pages.
35 * 1. Check for new or changed database schema and update it
36 * according to some predefined upgrade tables. (medium)
37 * 2. Check for new or changed (localized) pgsrc/ pages and ask
38 * for upgrading these. Check timestamps, upgrade silently or
39 * show diffs if existing. Overwrite or merge (easy)
40 * 3. Check for new or changed or deprecated index.php/config.ini settings
41 * and help in upgrading these. (hard)
42 * 3a Convert old-style index.php into config/config.ini. (easy)
43 * 4. Check for changed plugin invocation arguments. (hard)
44 * 5. Check for changed theme variables. (hard)
45 * 6. Convert the automatic update to a class-based multi-page
48 * @author: Reini Urban
50 require_once("lib/loadsave.php");
51 //define('DBADMIN_USER','rurban');
52 //define('DBADMIN_PASSWD','');
55 * TODO: check for the pgsrc_version number, not the revision
57 function doPgsrcUpdate(&$request,$pagename,$path,$filename) {
58 $dbi = $request->getDbh();
59 $page = $dbi->getPage($pagename);
60 if ($page->exists()) {
61 // check mtime: update automatically if pgsrc is newer
62 $rev = $page->getCurrentRevision();
63 $page_mtime = $rev->get('mtime');
64 $data = implode("", file($path."/".$filename));
65 if (($parts = ParseMimeifiedPages($data))) {
66 usort($parts, 'SortByPageVersion');
68 $pageinfo = $parts[0];
69 $stat = stat($path."/".$filename);
70 $new_mtime = @$pageinfo['versiondata']['mtime'];
72 $new_mtime = @$pageinfo['versiondata']['lastmodified'];
74 $new_mtime = @$pageinfo['pagedata']['date'];
76 $new_mtime = $stat[9];
77 if ($new_mtime > $page_mtime) {
78 echo "$path/$pagename: ",_("newer than the existing page."),
79 _(" replace "),"($new_mtime > $page_mtime)","<br />\n";
80 LoadAny($request,$path."/".$filename);
83 echo "$path/$pagename: ",_("older than the existing page."),
84 _(" skipped"),".<br />\n";
87 echo "$path/$pagename: ",("unknown format."),
88 _(" skipped"),".<br />\n";
91 echo sprintf(_("%s does not exist"),$pagename),"<br />\n";
92 LoadAny($request,$path."/".$filename);
97 /** need the english filename (required precondition: urlencode == urldecode)
98 * returns the plugin name.
100 function isActionPage($filename) {
101 static $special = array("DebugInfo" => "_BackendInfo",
102 "PhpWikiRecentChanges" => "RssFeed",
103 "ProjectSummary" => "RssFeed",
104 "RecentReleases" => "RssFeed",
106 $base = preg_replace("/\..{1,4}$/","",basename($filename));
107 if (isset($special[$base])) return $special[$base];
108 if (FindFile("lib/plugin/".$base.".php",true)) return $base;
112 function CheckActionPageUpdate(&$request) {
113 echo "<h3>",_("check for necessary ActionPage updates"),"</h3>\n";
114 $dbi = $request->getDbh();
115 $path = FindFile('pgsrc');
116 $pgsrc = new fileSet($path);
117 // most actionpages have the same name as the plugin
118 $loc_path = FindLocalizedFile('pgsrc');
119 foreach ($pgsrc->getFiles() as $filename) {
120 if (substr($filename,-1,1) == '~') continue;
121 $pagename = urldecode($filename);
122 if (isActionPage($filename)) {
123 $translation = gettext($pagename);
124 if ($translation == $pagename)
125 doPgsrcUpdate($request, $pagename, $path, $filename);
126 elseif (FindLocalizedFile('pgsrc/'.urlencode($translation),1))
127 doPgsrcUpdate($request, $translation, $loc_path,
128 urlencode($translation));
130 doPgsrcUpdate($request, $pagename, $path, $filename);
135 // see loadsave.php for saving new pages.
136 function CheckPgsrcUpdate(&$request) {
137 echo "<h3>",_("check for necessary pgsrc updates"),"</h3>\n";
138 $dbi = $request->getDbh();
139 $path = FindLocalizedFile(WIKI_PGSRC);
140 $pgsrc = new fileSet($path);
141 // fixme: verification, ...
143 foreach ($pgsrc->getFiles() as $filename) {
144 if (substr($filename,-1,1) == '~') continue;
145 $pagename = urldecode($filename);
146 // don't ever update the HomePage
147 if (defined(HOME_PAGE))
148 if ($pagename == HOME_PAGE) $isHomePage = true;
150 if ($pagename == _("HomePage")) $isHomePage = true;
151 if ($pagename == "HomePage") $isHomePage = true;
153 echo "$path/$pagename: ",_("always skip the HomePage."),
154 _(" skipped"),".<br />\n";
158 if (!isActionPage($filename)) {
159 doPgsrcUpdate($request,$pagename,$path,$filename);
166 * TODO: Search table definition in appropriate schema
168 * Supported: mysql and generic SQL, for ADODB and PearDB.
170 function installTable(&$dbh, $table, $backend_type) {
172 if (!in_array($DBParams['dbtype'],array('SQL','ADODB'))) return;
173 echo _("MISSING")," ... \n";
174 $backend = &$dbh->_backend->_dbh;
176 $schema = findFile("schemas/${backend_type}.sql");
178 echo " ",_("FAILED"),": ",sprintf(_("no schema %s found"),"schemas/${backend_type}.sql")," ... <br />\n";
182 extract($dbh->_backend->_table_names);
183 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
186 assert($session_tbl);
187 if ($backend_type == 'mysql') {
189 CREATE TABLE $session_tbl (
190 sess_id CHAR(32) NOT NULL DEFAULT '',
191 sess_data BLOB NOT NULL,
192 sess_date INT UNSIGNED NOT NULL,
193 sess_ip CHAR(15) NOT NULL,
194 PRIMARY KEY (sess_id),
199 CREATE TABLE $session_tbl (
200 sess_id CHAR(32) NOT NULL DEFAULT '',
201 sess_data ".($backend_type == 'pgsql'?'TEXT':'BLOB')." NOT NULL,
203 sess_ip CHAR(15) NOT NULL
205 $dbh->genericQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
207 $dbh->genericQuery("CREATE INDEX sess_date on session (sess_date)");
210 $user_tbl = $prefix.'user';
211 if ($backend_type == 'mysql') {
213 CREATE TABLE $user_tbl (
214 userid CHAR(48) BINARY NOT NULL UNIQUE,
215 passwd CHAR(48) BINARY DEFAULT '',
220 CREATE TABLE $user_tbl (
221 userid CHAR(48) NOT NULL,
222 passwd CHAR(48) DEFAULT ''
224 $dbh->genericQuery("CREATE UNIQUE INDEX userid ON $user_tbl (userid)");
228 $pref_tbl = $prefix.'pref';
229 if ($backend_type == 'mysql') {
231 CREATE TABLE $pref_tbl (
232 userid CHAR(48) BINARY NOT NULL UNIQUE,
233 prefs TEXT NULL DEFAULT '',
238 CREATE TABLE $pref_tbl (
239 userid CHAR(48) NOT NULL,
240 prefs TEXT NULL DEFAULT '',
242 $dbh->genericQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
246 $member_tbl = $prefix.'member';
247 if ($backend_type == 'mysql') {
249 CREATE TABLE $member_tbl (
250 userid CHAR(48) BINARY NOT NULL,
251 groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
257 CREATE TABLE $member_tbl (
258 userid CHAR(48) NOT NULL,
259 groupname CHAR(48) NOT NULL DEFAULT 'users',
261 $dbh->genericQuery("CREATE INDEX userid ON $member_tbl (userid)");
262 $dbh->genericQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
266 $rating_tbl = $prefix.'rating';
267 if ($backend_type == 'mysql') {
269 CREATE TABLE $rating_tbl (
270 dimension INT(4) NOT NULL,
271 raterpage INT(11) NOT NULL,
272 rateepage INT(11) NOT NULL,
273 ratingvalue FLOAT NOT NULL,
274 rateeversion INT(11) NOT NULL,
275 tstamp TIMESTAMP(14) NOT NULL,
276 PRIMARY KEY (dimension, raterpage, rateepage)
280 CREATE TABLE $rating_tbl (
281 dimension INT(4) NOT NULL,
282 raterpage INT(11) NOT NULL,
283 rateepage INT(11) NOT NULL,
284 ratingvalue FLOAT NOT NULL,
285 rateeversion INT(11) NOT NULL,
286 tstamp TIMESTAMP(14) NOT NULL,
288 $dbh->genericQuery("CREATE UNIQUE INDEX rating ON $rating_tbl (dimension, raterpage, rateepage)");
292 echo " ",_("CREATED"),"<br />\n";
296 * currently update only session, user, pref and member
297 * jeffs-hacks database api (around 1.3.2) later
298 * people should export/import their pages if using that old versions.
300 function CheckDatabaseUpdate(&$request) {
301 global $DBParams, $DBAuthParams;
302 if (!in_array($DBParams['dbtype'], array('SQL','ADODB'))) return;
303 echo "<h3>",_("check for necessary database updates"),"</h3>\n";
304 if (defined('DBADMIN_USER')) {
305 // if need to connect as the root user, for alter permissions
306 $AdminParams = $DBParams;
307 if ($DBParams['dbtype'] == 'SQL')
308 $dsn = DB::parseDSN($AdminParams['dsn']);
310 $dsn = parseDSN($AdminParams['dsn']);
311 $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
317 $dbh = WikiDB::open($AdminParams);
319 $dbh = &$request->_dbi;
321 $tables = $dbh->_backend->listOfTables();
322 $backend_type = $dbh->_backend->backendType();
323 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
324 extract($dbh->_backend->_table_names);
325 foreach (explode(':','session:user:pref:member') as $table) {
326 echo sprintf(_("check for table %s"), $table)," ...";
327 if (!in_array($prefix.$table, $tables)) {
328 installTable($dbh, $table, $backend_type);
330 echo _("OK")," <br />\n";
333 $backend = &$dbh->_backend->_dbh;
334 // 1.3.8 added session.sess_ip
335 if (phpwiki_version() >= 1030.08 and USE_DB_SESSION and isset($request->_dbsession)) {
336 echo _("check for new session.sess_ip column")," ... ";
337 $database = $dbh->_backend->database();
338 assert(!empty($DBParams['db_session_table']));
339 $session_tbl = $prefix . $DBParams['db_session_table'];
340 $sess_fields = $dbh->_backend->listOfFields($database, $session_tbl);
341 if (!strstr(strtolower(join(':', $sess_fields)),"sess_ip")) {
342 echo "<b>",_("ADDING"),"</b>"," ... ";
343 $dbh->genericQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
349 // 1.3.10 mysql requires page.id auto_increment
350 // mysql, mysqli or mysqlt
351 if (phpwiki_version() >= 1030.099 and substr($backend_type,0,5) == 'mysql') {
352 echo _("check for page.id auto_increment flag")," ...";
353 assert(!empty($page_tbl));
354 $database = $dbh->_backend->database();
355 $fields = mysql_list_fields($database, $page_tbl, $dbh->_backend->connection());
356 $columns = mysql_num_fields($fields);
357 for ($i = 0; $i < $columns; $i++) {
358 if (mysql_field_name($fields, $i) == 'id') {
359 $flags = mysql_field_flags($fields, $i);
360 //FIXME: something wrong with ADODB here!
361 if (!strstr(strtolower($flags),"auto_increment")) {
362 echo "<b>",_("ADDING"),"</b>"," ... ";
363 // MODIFY col_def valid since mysql 3.22.16,
364 // older mysql's need CHANGE old_col col_def
365 $dbh->genericQuery("ALTER TABLE $page_tbl CHANGE id id INT NOT NULL AUTO_INCREMENT");
366 $fields = mysql_list_fields($database, $page_tbl);
367 if (!strstr(strtolower(mysql_field_flags($fields, $i)),"auto_increment"))
368 echo " <b><font color=\"red\">",_("FAILED"),"</font></b><br />\n";
370 echo _("OK"),"<br />\n";
372 echo _("OK"),"<br />\n";
377 mysql_free_result($fields);
379 // check for mysql 4.1.x/5.0.0a binary search bug.
380 // http://bugs.mysql.com/bug.php?id=4398
381 // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
382 // confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
383 if (substr($backend_type,0,5) == 'mysql') {
384 echo _("check for mysql 4.1.x/5.0.0 binary search problem")," ...";
385 $result = mysql_query("SELECT VERSION()",$dbh->_backend->connection());
386 $row = mysql_fetch_row($result);
387 $mysql_version = $row[0];
388 $arr = explode('.',$mysql_version);
389 $version = (string)(($arr[0] * 100) + $arr[1]) . "." . (integer)$arr[2];
390 if ($version >= 401.0) {
391 $dbh->genericQuery("ALTER TABLE $page_tbl CHANGE pagename pagename VARCHAR(100) NOT NULL;");
392 echo sprintf(_("version <em>%s</em> <b>FIXED</b>"), $mysql_version),"<br />\n";
394 echo sprintf(_("version <em>%s</em> not affected"), $mysql_version),"<br />\n";
400 function fixConfigIni($match, $new) {
401 $file = FindFile("config/config.ini");
403 if (is_writable($file)) {
404 $in = fopen($file,"rb");
405 $out = fopen($tmp = tempnam(FindFile("uploads"),"cfg"),"wb");
407 $tmp = str_replace("/","\\",$tmp);
408 while ($s = fgets($in)) {
409 if (preg_match($match, $s)) {
410 $s = $new . (isWindows() ? "\r\n" : "\n");
418 echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
419 sprintf(_("%s not found"), $match);
422 @unlink("$file.bak");
423 @rename($file,"$file.bak");
424 if (rename($tmp, $file))
425 echo " <b>",_("FIXED"),"</b>";
427 echo " <b>",_("FAILED"),"</b>: ";
428 sprintf(_("couldn't move %s to %s"), $tmp, $file);
434 echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
435 sprintf(_("%s is not writable"), $file);
440 function CheckConfigUpdate(&$request) {
441 echo "<h3>",_("check for necessary config updates"),"</h3>\n";
442 echo _("check for old CACHE_CONTROL = NONE")," ... ";
443 if (defined('CACHE_CONTROL') and CACHE_CONTROL == '') {
444 echo "<br /> ",_("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'")," ...";
445 fixConfigIni("/^\s*CACHE_CONTROL\s*=\s*NONE/","CACHE_CONTROL = NO_CACHE");
455 * Upgrade: Base class for multipage worksteps
456 * identify, validate, display options, next step
461 class Upgrade_CheckPgsrc extends Upgrade {
464 class Upgrade_CheckDatabaseUpdate extends Upgrade {
467 // TODO: At which step are we?
468 // validate and do it again or go on with next step.
470 /** entry function from lib/main.php
472 function DoUpgrade($request) {
474 if (!$request->_user->isAdmin()) {
475 $request->_notAuthorized(WIKIAUTH_ADMIN);
477 HTML::div(array('class' => 'disabled-plugin'),
478 fmt("Upgrade disabled: user != isAdmin")));
482 StartLoadDump($request, _("Upgrading this PhpWiki"));
483 CheckActionPageUpdate($request);
484 CheckDatabaseUpdate($request);
485 CheckPgsrcUpdate($request);
486 //CheckThemeUpdate($request);
487 CheckConfigUpdate($request);
488 EndLoadDump($request);
493 $Log: not supported by cvs2svn $
494 Revision 1.21 2004/07/03 16:51:05 rurban
495 optional DBADMIN_USER:DBADMIN_PASSWD for action=upgrade (if no ALTER permission)
496 added atomic mysql REPLACE for PearDB as in ADODB
497 fixed _lock_tables typo links => link
498 fixes unserialize ADODB bug in line 180
500 Revision 1.20 2004/07/03 14:48:18 rurban
501 Tested new mysql 4.1.3-beta: binary search bug as fixed.
502 => fixed action=upgrade,
503 => version check in PearDB also (as in ADODB)
505 Revision 1.19 2004/06/19 12:19:09 rurban
506 slightly improved docs
508 Revision 1.18 2004/06/19 11:47:17 rurban
509 added CheckConfigUpdate: CACHE_CONTROL = NONE => NO_CACHE
511 Revision 1.17 2004/06/17 11:31:50 rurban
512 check necessary localized actionpages
514 Revision 1.16 2004/06/16 10:38:58 rurban
515 Disallow refernces in calls if the declaration is a reference
516 ("allow_call_time_pass_reference clean").
517 PhpWiki is now allow_call_time_pass_reference = Off clean,
518 but several external libraries may not.
519 In detail these libs look to be affected (not tested):
523 Revision 1.15 2004/06/07 19:50:40 rurban
524 add owner field to mimified dump
526 Revision 1.14 2004/06/07 18:38:18 rurban
527 added mysql 4.1.x search fix
529 Revision 1.13 2004/06/04 20:32:53 rurban
530 Several locale related improvements suggested by Pierrick Meignen
531 LDAP fix by John Cole
532 reanable admin check without ENABLE_PAGEPERM in the admin plugins
534 Revision 1.12 2004/05/18 13:59:15 rurban
535 rename simpleQuery to genericQuery
537 Revision 1.11 2004/05/15 13:06:17 rurban
538 skip the HomePage, at first upgrade the ActionPages, then the database, then the rest
540 Revision 1.10 2004/05/15 01:19:41 rurban
541 upgrade prefix fix by Kai Krakow
543 Revision 1.9 2004/05/14 11:33:03 rurban
544 version updated to 1.3.11pre
545 upgrade stability fix
547 Revision 1.8 2004/05/12 10:49:55 rurban
548 require_once fix for those libs which are loaded before FileFinder and
549 its automatic include_path fix, and where require_once doesn't grok
550 dirname(__FILE__) != './lib'
551 upgrade fix with PearDB
552 navbar.tmpl: remove spaces for IE button alignment
554 Revision 1.7 2004/05/06 17:30:38 rurban
555 CategoryGroup: oops, dos2unix eol
556 improved phpwiki_version:
557 pre -= .0001 (1.3.10pre: 1030.099)
558 -p1 += .001 (1.3.9-p1: 1030.091)
559 improved InstallTable for mysql and generic SQL versions and all newer tables so far.
560 abstracted more ADODB/PearDB methods for action=upgrade stuff:
561 backend->backendType(), backend->database(),
562 backend->listOfFields(),
563 backend->listOfTables(),
565 Revision 1.6 2004/05/03 15:05:36 rurban
568 Revision 1.4 2004/05/02 21:26:38 rurban
569 limit user session data (HomePageHandle and auth_dbi have to invalidated anyway)
570 because they will not survive db sessions, if too large.
571 extended action=upgrade
572 some WikiTranslation button work
573 revert WIKIAUTH_UNOBTAINABLE (need it for main.php)
574 some temp. session debug statements
576 Revision 1.3 2004/04/29 22:33:30 rurban
577 fixed sf.net bug #943366 (Kai Krakow)
578 couldn't load localized url-undecoded pagenames
580 Revision 1.2 2004/03/12 15:48:07 rurban
581 fixed explodePageList: wrong sortby argument order in UnfoldSubpages
582 simplified lib/stdlib.php:explodePageList
591 // c-hanging-comment-ender-p: nil
592 // indent-tabs-mode: nil