2 rcs_id('$Id: upgrade.php,v 1.24 2004-07-05 13:56:22 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 * TODO: overwrite=1 link on edit conflicts at end of page to overwrite all.
50 * @author: Reini Urban
52 require_once("lib/loadsave.php");
53 //define('DBADMIN_USER','rurban');
54 //define('DBADMIN_PASSWD','');
57 * TODO: check for the pgsrc_version number, not the revision mtime only
59 function doPgsrcUpdate(&$request,$pagename,$path,$filename) {
60 $dbi = $request->getDbh();
61 $page = $dbi->getPage($pagename);
62 if ($page->exists()) {
63 // check mtime: update automatically if pgsrc is newer
64 $rev = $page->getCurrentRevision();
65 $page_mtime = $rev->get('mtime');
66 $data = implode("", file($path."/".$filename));
67 if (($parts = ParseMimeifiedPages($data))) {
68 usort($parts, 'SortByPageVersion');
70 $pageinfo = $parts[0];
71 $stat = stat($path."/".$filename);
72 $new_mtime = @$pageinfo['versiondata']['mtime'];
74 $new_mtime = @$pageinfo['versiondata']['lastmodified'];
76 $new_mtime = @$pageinfo['pagedata']['date'];
78 $new_mtime = $stat[9];
79 if ($new_mtime > $page_mtime) {
80 echo "$path/$pagename: ",_("newer than the existing page."),
81 _(" replace "),"($new_mtime > $page_mtime)","<br />\n";
82 LoadAny($request,$path."/".$filename);
85 echo "$path/$pagename: ",_("older than the existing page."),
86 _(" skipped"),".<br />\n";
89 echo "$path/$pagename: ",("unknown format."),
90 _(" skipped"),".<br />\n";
93 echo sprintf(_("%s does not exist"),$pagename),"<br />\n";
94 LoadAny($request,$path."/".$filename);
99 /** need the english filename (required precondition: urlencode == urldecode)
100 * returns the plugin name.
102 function isActionPage($filename) {
103 static $special = array("DebugInfo" => "_BackendInfo",
104 "PhpWikiRecentChanges" => "RssFeed",
105 "ProjectSummary" => "RssFeed",
106 "RecentReleases" => "RssFeed",
108 $base = preg_replace("/\..{1,4}$/","",basename($filename));
109 if (isset($special[$base])) return $special[$base];
110 if (FindFile("lib/plugin/".$base.".php",true)) return $base;
114 function CheckActionPageUpdate(&$request) {
115 echo "<h3>",_("check for necessary ActionPage updates"),"</h3>\n";
116 $dbi = $request->getDbh();
117 $path = FindFile('pgsrc');
118 $pgsrc = new fileSet($path);
119 // most actionpages have the same name as the plugin
120 $loc_path = FindLocalizedFile('pgsrc');
121 foreach ($pgsrc->getFiles() as $filename) {
122 if (substr($filename,-1,1) == '~') continue;
123 $pagename = urldecode($filename);
124 if (isActionPage($filename)) {
125 $translation = gettext($pagename);
126 if ($translation == $pagename)
127 doPgsrcUpdate($request, $pagename, $path, $filename);
128 elseif (FindLocalizedFile('pgsrc/'.urlencode($translation),1))
129 doPgsrcUpdate($request, $translation, $loc_path,
130 urlencode($translation));
132 doPgsrcUpdate($request, $pagename, $path, $filename);
137 // see loadsave.php for saving new pages.
138 function CheckPgsrcUpdate(&$request) {
139 echo "<h3>",_("check for necessary pgsrc updates"),"</h3>\n";
140 $dbi = $request->getDbh();
141 $path = FindLocalizedFile(WIKI_PGSRC);
142 $pgsrc = new fileSet($path);
143 // fixme: verification, ...
145 foreach ($pgsrc->getFiles() as $filename) {
146 if (substr($filename,-1,1) == '~') continue;
147 $pagename = urldecode($filename);
148 // don't ever update the HomePage
149 if (defined(HOME_PAGE))
150 if ($pagename == HOME_PAGE) $isHomePage = true;
152 if ($pagename == _("HomePage")) $isHomePage = true;
153 if ($pagename == "HomePage") $isHomePage = true;
155 echo "$path/$pagename: ",_("always skip the HomePage."),
156 _(" skipped"),".<br />\n";
160 if (!isActionPage($filename)) {
161 doPgsrcUpdate($request,$pagename,$path,$filename);
168 * TODO: Search table definition in appropriate schema
170 * Supported: mysql and generic SQL, for ADODB and PearDB.
172 function installTable(&$dbh, $table, $backend_type) {
174 if (!in_array($DBParams['dbtype'],array('SQL','ADODB'))) return;
175 echo _("MISSING")," ... \n";
176 $backend = &$dbh->_backend->_dbh;
178 $schema = findFile("schemas/${backend_type}.sql");
180 echo " ",_("FAILED"),": ",sprintf(_("no schema %s found"),"schemas/${backend_type}.sql")," ... <br />\n";
184 extract($dbh->_backend->_table_names);
185 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
188 assert($session_tbl);
189 if ($backend_type == 'mysql') {
191 CREATE TABLE $session_tbl (
192 sess_id CHAR(32) NOT NULL DEFAULT '',
193 sess_data BLOB NOT NULL,
194 sess_date INT UNSIGNED NOT NULL,
195 sess_ip CHAR(15) NOT NULL,
196 PRIMARY KEY (sess_id),
201 CREATE TABLE $session_tbl (
202 sess_id CHAR(32) NOT NULL DEFAULT '',
203 sess_data ".($backend_type == 'pgsql'?'TEXT':'BLOB')." NOT NULL,
205 sess_ip CHAR(15) NOT NULL
207 $dbh->genericQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
209 $dbh->genericQuery("CREATE INDEX sess_date on session (sess_date)");
212 $user_tbl = $prefix.'user';
213 if ($backend_type == 'mysql') {
215 CREATE TABLE $user_tbl (
216 userid CHAR(48) BINARY NOT NULL UNIQUE,
217 passwd CHAR(48) BINARY DEFAULT '',
222 CREATE TABLE $user_tbl (
223 userid CHAR(48) NOT NULL,
224 passwd CHAR(48) DEFAULT ''
226 $dbh->genericQuery("CREATE UNIQUE INDEX userid ON $user_tbl (userid)");
230 $pref_tbl = $prefix.'pref';
231 if ($backend_type == 'mysql') {
233 CREATE TABLE $pref_tbl (
234 userid CHAR(48) BINARY NOT NULL UNIQUE,
235 prefs TEXT NULL DEFAULT '',
240 CREATE TABLE $pref_tbl (
241 userid CHAR(48) NOT NULL,
242 prefs TEXT NULL DEFAULT '',
244 $dbh->genericQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
248 $member_tbl = $prefix.'member';
249 if ($backend_type == 'mysql') {
251 CREATE TABLE $member_tbl (
252 userid CHAR(48) BINARY NOT NULL,
253 groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
259 CREATE TABLE $member_tbl (
260 userid CHAR(48) NOT NULL,
261 groupname CHAR(48) NOT NULL DEFAULT 'users',
263 $dbh->genericQuery("CREATE INDEX userid ON $member_tbl (userid)");
264 $dbh->genericQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
268 $rating_tbl = $prefix.'rating';
269 if ($backend_type == 'mysql') {
271 CREATE TABLE $rating_tbl (
272 dimension INT(4) NOT NULL,
273 raterpage INT(11) NOT NULL,
274 rateepage INT(11) NOT NULL,
275 ratingvalue FLOAT NOT NULL,
276 rateeversion INT(11) NOT NULL,
277 tstamp TIMESTAMP(14) NOT NULL,
278 PRIMARY KEY (dimension, raterpage, rateepage)
282 CREATE TABLE $rating_tbl (
283 dimension INT(4) NOT NULL,
284 raterpage INT(11) NOT NULL,
285 rateepage INT(11) NOT NULL,
286 ratingvalue FLOAT NOT NULL,
287 rateeversion INT(11) NOT NULL,
288 tstamp TIMESTAMP(14) NOT NULL,
290 $dbh->genericQuery("CREATE UNIQUE INDEX rating ON $rating_tbl (dimension, raterpage, rateepage)");
294 echo " ",_("CREATED"),"<br />\n";
298 * currently update only session, user, pref and member
299 * jeffs-hacks database api (around 1.3.2) later
300 * people should export/import their pages if using that old versions.
302 function CheckDatabaseUpdate(&$request) {
303 global $DBParams, $DBAuthParams;
304 if (!in_array($DBParams['dbtype'], array('SQL','ADODB'))) return;
305 echo "<h3>",_("check for necessary database updates"),"</h3>\n";
306 if (defined('DBADMIN_USER') and DBADMIN_USER) {
307 // if need to connect as the root user, for alter permissions
308 $AdminParams = $DBParams;
309 if ($DBParams['dbtype'] == 'SQL')
310 $dsn = DB::parseDSN($AdminParams['dsn']);
312 $dsn = parseDSN($AdminParams['dsn']);
313 $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
319 $dbh = WikiDB::open($AdminParams);
321 $dbh = &$request->_dbi;
323 $tables = $dbh->_backend->listOfTables();
324 $backend_type = $dbh->_backend->backendType();
325 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
326 extract($dbh->_backend->_table_names);
327 foreach (explode(':','session:user:pref:member') as $table) {
328 echo sprintf(_("check for table %s"), $table)," ...";
329 if (!in_array($prefix.$table, $tables)) {
330 installTable($dbh, $table, $backend_type);
332 echo _("OK")," <br />\n";
335 $backend = &$dbh->_backend->_dbh;
336 // 1.3.8 added session.sess_ip
337 if (phpwiki_version() >= 1030.08 and USE_DB_SESSION and isset($request->_dbsession)) {
338 echo _("check for new session.sess_ip column")," ... ";
339 $database = $dbh->_backend->database();
340 assert(!empty($DBParams['db_session_table']));
341 $session_tbl = $prefix . $DBParams['db_session_table'];
342 $sess_fields = $dbh->_backend->listOfFields($database, $session_tbl);
343 if (!strstr(strtolower(join(':', $sess_fields)),"sess_ip")) {
344 echo "<b>",_("ADDING"),"</b>"," ... ";
345 $dbh->genericQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
351 // 1.3.10 mysql requires page.id auto_increment
352 // mysql, mysqli or mysqlt
353 if (phpwiki_version() >= 1030.099 and substr($backend_type,0,5) == 'mysql') {
354 echo _("check for page.id auto_increment flag")," ...";
355 assert(!empty($page_tbl));
356 $database = $dbh->_backend->database();
357 $fields = mysql_list_fields($database, $page_tbl, $dbh->_backend->connection());
358 $columns = mysql_num_fields($fields);
359 for ($i = 0; $i < $columns; $i++) {
360 if (mysql_field_name($fields, $i) == 'id') {
361 $flags = mysql_field_flags($fields, $i);
362 //FIXME: something wrong with ADODB here!
363 if (!strstr(strtolower($flags),"auto_increment")) {
364 echo "<b>",_("ADDING"),"</b>"," ... ";
365 // MODIFY col_def valid since mysql 3.22.16,
366 // older mysql's need CHANGE old_col col_def
367 $dbh->genericQuery("ALTER TABLE $page_tbl CHANGE id id INT NOT NULL AUTO_INCREMENT");
368 $fields = mysql_list_fields($database, $page_tbl);
369 if (!strstr(strtolower(mysql_field_flags($fields, $i)),"auto_increment"))
370 echo " <b><font color=\"red\">",_("FAILED"),"</font></b><br />\n";
372 echo _("OK"),"<br />\n";
374 echo _("OK"),"<br />\n";
379 mysql_free_result($fields);
381 // check for mysql 4.1.x/5.0.0a binary search bug.
382 // http://bugs.mysql.com/bug.php?id=4398
383 // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
384 // confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
385 if (substr($backend_type,0,5) == 'mysql') {
386 echo _("check for mysql 4.1.x/5.0.0 binary search problem")," ...";
387 $result = mysql_query("SELECT VERSION()",$dbh->_backend->connection());
388 $row = mysql_fetch_row($result);
389 $mysql_version = $row[0];
390 $arr = explode('.',$mysql_version);
391 $version = (string)(($arr[0] * 100) + $arr[1]) . "." . (integer)$arr[2];
392 if ($version >= 401.0) {
393 $dbh->genericQuery("ALTER TABLE $page_tbl CHANGE pagename pagename VARCHAR(100) NOT NULL;");
394 echo sprintf(_("version <em>%s</em> <b>FIXED</b>"), $mysql_version),"<br />\n";
396 echo sprintf(_("version <em>%s</em> not affected"), $mysql_version),"<br />\n";
402 function fixConfigIni($match, $new) {
403 $file = FindFile("config/config.ini");
405 if (is_writable($file)) {
406 $in = fopen($file,"rb");
407 $out = fopen($tmp = tempnam(FindFile("uploads"),"cfg"),"wb");
409 $tmp = str_replace("/","\\",$tmp);
410 while ($s = fgets($in)) {
411 if (preg_match($match, $s)) {
412 $s = $new . (isWindows() ? "\r\n" : "\n");
420 echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
421 sprintf(_("%s not found"), $match);
424 @unlink("$file.bak");
425 @rename($file,"$file.bak");
426 if (rename($tmp, $file))
427 echo " <b>",_("FIXED"),"</b>";
429 echo " <b>",_("FAILED"),"</b>: ";
430 sprintf(_("couldn't move %s to %s"), $tmp, $file);
436 echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
437 sprintf(_("%s is not writable"), $file);
442 function CheckConfigUpdate(&$request) {
443 echo "<h3>",_("check for necessary config updates"),"</h3>\n";
444 echo _("check for old CACHE_CONTROL = NONE")," ... ";
445 if (defined('CACHE_CONTROL') and CACHE_CONTROL == '') {
446 echo "<br /> ",_("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'")," ...";
447 fixConfigIni("/^\s*CACHE_CONTROL\s*=\s*NONE/","CACHE_CONTROL = NO_CACHE");
457 * Upgrade: Base class for multipage worksteps
458 * identify, validate, display options, next step
463 class Upgrade_CheckPgsrc extends Upgrade {
466 class Upgrade_CheckDatabaseUpdate extends Upgrade {
469 // TODO: At which step are we?
470 // validate and do it again or go on with next step.
472 /** entry function from lib/main.php
474 function DoUpgrade($request) {
476 if (!$request->_user->isAdmin()) {
477 $request->_notAuthorized(WIKIAUTH_ADMIN);
479 HTML::div(array('class' => 'disabled-plugin'),
480 fmt("Upgrade disabled: user != isAdmin")));
484 StartLoadDump($request, _("Upgrading this PhpWiki"));
485 CheckActionPageUpdate($request);
486 CheckDatabaseUpdate($request);
487 CheckPgsrcUpdate($request);
488 //CheckThemeUpdate($request);
489 CheckConfigUpdate($request);
490 EndLoadDump($request);
495 $Log: not supported by cvs2svn $
496 Revision 1.23 2004/07/04 10:28:06 rurban
499 Revision 1.22 2004/07/03 17:21:28 rurban
500 updated docs: submitted new mysql bugreport (#1491 did not fix it)
502 Revision 1.21 2004/07/03 16:51:05 rurban
503 optional DBADMIN_USER:DBADMIN_PASSWD for action=upgrade (if no ALTER permission)
504 added atomic mysql REPLACE for PearDB as in ADODB
505 fixed _lock_tables typo links => link
506 fixes unserialize ADODB bug in line 180
508 Revision 1.20 2004/07/03 14:48:18 rurban
509 Tested new mysql 4.1.3-beta: binary search bug as fixed.
510 => fixed action=upgrade,
511 => version check in PearDB also (as in ADODB)
513 Revision 1.19 2004/06/19 12:19:09 rurban
514 slightly improved docs
516 Revision 1.18 2004/06/19 11:47:17 rurban
517 added CheckConfigUpdate: CACHE_CONTROL = NONE => NO_CACHE
519 Revision 1.17 2004/06/17 11:31:50 rurban
520 check necessary localized actionpages
522 Revision 1.16 2004/06/16 10:38:58 rurban
523 Disallow refernces in calls if the declaration is a reference
524 ("allow_call_time_pass_reference clean").
525 PhpWiki is now allow_call_time_pass_reference = Off clean,
526 but several external libraries may not.
527 In detail these libs look to be affected (not tested):
531 Revision 1.15 2004/06/07 19:50:40 rurban
532 add owner field to mimified dump
534 Revision 1.14 2004/06/07 18:38:18 rurban
535 added mysql 4.1.x search fix
537 Revision 1.13 2004/06/04 20:32:53 rurban
538 Several locale related improvements suggested by Pierrick Meignen
539 LDAP fix by John Cole
540 reanable admin check without ENABLE_PAGEPERM in the admin plugins
542 Revision 1.12 2004/05/18 13:59:15 rurban
543 rename simpleQuery to genericQuery
545 Revision 1.11 2004/05/15 13:06:17 rurban
546 skip the HomePage, at first upgrade the ActionPages, then the database, then the rest
548 Revision 1.10 2004/05/15 01:19:41 rurban
549 upgrade prefix fix by Kai Krakow
551 Revision 1.9 2004/05/14 11:33:03 rurban
552 version updated to 1.3.11pre
553 upgrade stability fix
555 Revision 1.8 2004/05/12 10:49:55 rurban
556 require_once fix for those libs which are loaded before FileFinder and
557 its automatic include_path fix, and where require_once doesn't grok
558 dirname(__FILE__) != './lib'
559 upgrade fix with PearDB
560 navbar.tmpl: remove spaces for IE button alignment
562 Revision 1.7 2004/05/06 17:30:38 rurban
563 CategoryGroup: oops, dos2unix eol
564 improved phpwiki_version:
565 pre -= .0001 (1.3.10pre: 1030.099)
566 -p1 += .001 (1.3.9-p1: 1030.091)
567 improved InstallTable for mysql and generic SQL versions and all newer tables so far.
568 abstracted more ADODB/PearDB methods for action=upgrade stuff:
569 backend->backendType(), backend->database(),
570 backend->listOfFields(),
571 backend->listOfTables(),
573 Revision 1.6 2004/05/03 15:05:36 rurban
576 Revision 1.4 2004/05/02 21:26:38 rurban
577 limit user session data (HomePageHandle and auth_dbi have to invalidated anyway)
578 because they will not survive db sessions, if too large.
579 extended action=upgrade
580 some WikiTranslation button work
581 revert WIKIAUTH_UNOBTAINABLE (need it for main.php)
582 some temp. session debug statements
584 Revision 1.3 2004/04/29 22:33:30 rurban
585 fixed sf.net bug #943366 (Kai Krakow)
586 couldn't load localized url-undecoded pagenames
588 Revision 1.2 2004/03/12 15:48:07 rurban
589 fixed explodePageList: wrong sortby argument order in UnfoldSubpages
590 simplified lib/stdlib.php:explodePageList
599 // c-hanging-comment-ender-p: nil
600 // indent-tabs-mode: nil