]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/upgrade.php
clarify messages
[SourceForge/phpwiki.git] / lib / upgrade.php
1 <?php //-*-php-*-
2 rcs_id('$Id: upgrade.php,v 1.40 2005-01-30 23:22:17 rurban Exp $');
3 /*
4  Copyright 2004,2005 $ThePhpWikiProgrammingTeam
5
6  This file is part of PhpWiki.
7
8  PhpWiki is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation; either version 2 of the License, or
11  (at your option) any later version.
12
13  PhpWiki is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  GNU General Public License for more details.
17
18  You should have received a copy of the GNU General Public License
19  along with PhpWiki; if not, write to the Free Software
20  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 /**
24  * Upgrade the WikiDB and config settings after installing a new 
25  * PhpWiki upgrade.
26  * Status: experimental, no queries for verification yet, 
27  *         no merge conflict resolution (patch?), just overwrite.
28  *
29  * Installation on an existing PhpWiki database needs some 
30  * additional worksteps. Each step will require multiple pages.
31  *
32  * This is the plan:
33  *  1. Check for new or changed database schema and update it 
34  *     according to some predefined upgrade tables. (medium, complete)
35  *  2. Check for new or changed (localized) pgsrc/ pages and ask 
36  *     for upgrading these. Check timestamps, upgrade silently or 
37  *     show diffs if existing. Overwrite or merge (easy, complete)
38  *  3. Check for new or changed or deprecated index.php/config.ini settings
39  *     and help in upgrading these. (hard)
40  *  3a Convert old-style index.php into config/config.ini. (easy)
41  *  4. Check for changed plugin invocation arguments. (hard)
42  *  5. Check for changed theme variables. (hard)
43  *  6. Convert the single-request upgrade to a class-based multi-page 
44  *     version. (hard)
45
46  * Done: overwrite=1 link on edit conflicts at first occurence "Overwrite all".
47  *
48  * @author: Reini Urban
49  */
50 require_once("lib/loadsave.php");
51
52 /**
53  * TODO: check for the pgsrc_version number, not the revision mtime only
54  */
55 function doPgsrcUpdate(&$request,$pagename,$path,$filename) {
56     $dbi = $request->getDbh(); 
57     $page = $dbi->getPage($pagename);
58     if ($page->exists()) {
59         // check mtime: update automatically if pgsrc is newer
60         $rev = $page->getCurrentRevision();
61         $page_mtime = $rev->get('mtime');
62         $data  = implode("", file($path."/".$filename));
63         if (($parts = ParseMimeifiedPages($data))) {
64             usort($parts, 'SortByPageVersion');
65             reset($parts);
66             $pageinfo = $parts[0];
67             $stat  = stat($path."/".$filename);
68             $new_mtime = @$pageinfo['versiondata']['mtime'];
69             if (!$new_mtime)
70                 $new_mtime = @$pageinfo['versiondata']['lastmodified'];
71             if (!$new_mtime)
72                 $new_mtime = @$pageinfo['pagedata']['date'];
73             if (!$new_mtime)
74                 $new_mtime = $stat[9];
75             if ($new_mtime > $page_mtime) {
76                 echo "$path/$pagename: ",_("newer than the existing page."),
77                     _(" replace "),"($new_mtime &gt; $page_mtime)","<br />\n";
78                 LoadAny($request,$path."/".$filename);
79                 echo "<br />\n";
80             } else {
81                 echo "$path/$pagename: ",_("older than the existing page."),
82                     _(" skipped"),".<br />\n";
83             }
84         } else {
85             echo "$path/$pagename: ",("unknown format."),
86                     _(" skipped"),".<br />\n";
87         }
88     } else {
89         echo sprintf(_("%s does not exist"),$pagename),"<br />\n";
90         LoadAny($request,$path."/".$filename);
91         echo "<br />\n";
92     }
93 }
94
95 /** Need the english filename (required precondition: urlencode == urldecode).
96  *  Returns the plugin name.
97  */ 
98 function isActionPage($filename) {
99     static $special = array("DebugInfo"         => "_BackendInfo",
100                             "PhpWikiRecentChanges" => "RssFeed",
101                             "ProjectSummary"    => "RssFeed",
102                             "RecentReleases"    => "RssFeed",
103                             "InterWikiMap"      => "InterWikiMap",
104                             );
105     $base = preg_replace("/\..{1,4}$/","",basename($filename));
106     if (isset($special[$base])) return $special[$base];
107     if (FindFile("lib/plugin/".$base.".php",true)) return $base;
108     else return false;
109 }
110
111 function CheckActionPageUpdate(&$request) {
112     echo "<h3>",_("check for necessary ActionPage updates"),"</h3>\n";
113     $dbi = $request->getDbh(); 
114     $path = FindFile('pgsrc');
115     $pgsrc = new fileSet($path);
116     // most actionpages have the same name as the plugin
117     $loc_path = FindLocalizedFile('pgsrc');
118     foreach ($pgsrc->getFiles() as $filename) {
119         if (substr($filename,-1,1) == '~') continue;
120         $pagename = urldecode($filename);
121         if (isActionPage($filename)) {
122             $translation = gettext($pagename);
123             if ($translation == $pagename)
124                 doPgsrcUpdate($request, $pagename, $path, $filename);
125             elseif (FindLocalizedFile('pgsrc/'.urlencode($translation),1))
126                 doPgsrcUpdate($request, $translation, $loc_path, 
127                               urlencode($translation));
128             else
129                 doPgsrcUpdate($request, $pagename, $path, $filename);
130         }
131     }
132 }
133
134 // see loadsave.php for saving new pages.
135 function CheckPgsrcUpdate(&$request) {
136     echo "<h3>",_("check for necessary pgsrc updates"),"</h3>\n";
137     $dbi = $request->getDbh(); 
138     $path = FindLocalizedFile(WIKI_PGSRC);
139     $pgsrc = new fileSet($path);
140     // fixme: verification, ...
141     $isHomePage = false;
142     foreach ($pgsrc->getFiles() as $filename) {
143         if (substr($filename,-1,1) == '~') continue;
144         $pagename = urldecode($filename);
145         // don't ever update the HomePage
146         if (defined(HOME_PAGE))
147             if ($pagename == HOME_PAGE) $isHomePage = true;
148         else
149             if ($pagename == _("HomePage")) $isHomePage = true;
150         if ($pagename == "HomePage") $isHomePage = true;
151         if ($isHomePage) {
152             echo "$path/$pagename: ",_("always skip the HomePage."),
153                 _(" skipped"),".<br />\n";
154             $isHomePage = false;
155             continue;
156         }
157         if (!isActionPage($filename)) {
158             doPgsrcUpdate($request,$pagename,$path,$filename);
159         }
160     }
161     return;
162 }
163
164 /**
165  * TODO: Search table definition in appropriate schema
166  *       and create it.
167  * Supported: mysql and generic SQL, for ADODB and PearDB.
168  */
169 function installTable(&$dbh, $table, $backend_type) {
170     global $DBParams;
171     if (!in_array($DBParams['dbtype'],array('SQL','ADODB'))) return;
172     echo _("MISSING")," ... \n";
173     $backend = &$dbh->_backend->_dbh;
174     /*
175     $schema = findFile("schemas/${backend_type}.sql");
176     if (!$schema) {
177         echo "  ",_("FAILED"),": ",sprintf(_("no schema %s found"),"schemas/${backend_type}.sql")," ... <br />\n";
178         return false;
179     }
180     */
181     extract($dbh->_backend->_table_names);
182     $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
183     switch ($table) {
184     case 'session':
185         assert($session_tbl);
186         if ($backend_type == 'mysql') {
187             $dbh->genericSqlQuery("
188 CREATE TABLE $session_tbl (
189         sess_id         CHAR(32) NOT NULL DEFAULT '',
190         sess_data       BLOB NOT NULL,
191         sess_date       INT UNSIGNED NOT NULL,
192         sess_ip         CHAR(15) NOT NULL,
193         PRIMARY KEY (sess_id),
194         INDEX (sess_date)
195 )");
196         } else {
197             $dbh->genericSqlQuery("
198 CREATE TABLE $session_tbl (
199         sess_id         CHAR(32) NOT NULL DEFAULT '',
200         sess_data       ".($backend_type == 'pgsql'?'TEXT':'BLOB')." NOT NULL,
201         sess_date       INT,
202         sess_ip         CHAR(15) NOT NULL
203 )");
204             $dbh->genericSqlQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
205         }
206         $dbh->genericSqlQuery("CREATE INDEX sess_date on session (sess_date)");
207         echo "  ",_("CREATED");
208         break;
209     case 'user':
210         $user_tbl = $prefix.'user';
211         if ($backend_type == 'mysql') {
212             $dbh->genericSqlQuery("
213 CREATE TABLE $user_tbl (
214         userid  CHAR(48) BINARY NOT NULL UNIQUE,
215         passwd  CHAR(48) BINARY DEFAULT '',
216         PRIMARY KEY (userid)
217 )");
218         } else {
219             $dbh->genericSqlQuery("
220 CREATE TABLE $user_tbl (
221         userid  CHAR(48) NOT NULL,
222         passwd  CHAR(48) DEFAULT ''
223 )");
224             $dbh->genericSqlQuery("CREATE UNIQUE INDEX userid ON $user_tbl (userid)");
225         }
226         echo "  ",_("CREATED");
227         break;
228     case 'pref':
229         $pref_tbl = $prefix.'pref';
230         if ($backend_type == 'mysql') {
231             $dbh->genericSqlQuery("
232 CREATE TABLE $pref_tbl (
233         userid  CHAR(48) BINARY NOT NULL UNIQUE,
234         prefs   TEXT NULL DEFAULT '',
235         PRIMARY KEY (userid)
236 )");
237         } else {
238             $dbh->genericSqlQuery("
239 CREATE TABLE $pref_tbl (
240         userid  CHAR(48) NOT NULL,
241         prefs   TEXT NULL DEFAULT '',
242 )");
243             $dbh->genericSqlQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
244         }
245         echo "  ",_("CREATED");
246         break;
247     case 'member':
248         $member_tbl = $prefix.'member';
249         if ($backend_type == 'mysql') {
250             $dbh->genericSqlQuery("
251 CREATE TABLE $member_tbl (
252         userid    CHAR(48) BINARY NOT NULL,
253         groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
254         INDEX (userid),
255         INDEX (groupname)
256 )");
257         } else {
258             $dbh->genericSqlQuery("
259 CREATE TABLE $member_tbl (
260         userid    CHAR(48) NOT NULL,
261         groupname CHAR(48) NOT NULL DEFAULT 'users',
262 )");
263             $dbh->genericSqlQuery("CREATE INDEX userid ON $member_tbl (userid)");
264             $dbh->genericSqlQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
265         }
266         echo "  ",_("CREATED");
267         break;
268     case 'rating':
269         $rating_tbl = $prefix.'rating';
270         if ($backend_type == 'mysql') {
271             $dbh->genericSqlQuery("
272 CREATE TABLE $rating_tbl (
273         dimension INT(4) NOT NULL,
274         raterpage INT(11) NOT NULL,
275         rateepage INT(11) NOT NULL,
276         ratingvalue FLOAT NOT NULL,
277         rateeversion INT(11) NOT NULL,
278         tstamp TIMESTAMP(14) NOT NULL,
279         PRIMARY KEY (dimension, raterpage, rateepage)
280 )");
281         } else {
282             $dbh->genericSqlQuery("
283 CREATE TABLE $rating_tbl (
284         dimension INT(4) NOT NULL,
285         raterpage INT(11) NOT NULL,
286         rateepage INT(11) NOT NULL,
287         ratingvalue FLOAT NOT NULL,
288         rateeversion INT(11) NOT NULL,
289         tstamp TIMESTAMP(14) NOT NULL,
290 )");
291             $dbh->genericSqlQuery("CREATE UNIQUE INDEX rating ON $rating_tbl (dimension, raterpage, rateepage)");
292         }
293         echo "  ",_("CREATED");
294         break;
295     case 'accesslog':
296         $log_tbl = $prefix.'accesslog';
297         // fields according to http://www.outoforder.cc/projects/apache/mod_log_sql/docs-2.0/#id2756178
298         /*
299 A       User Agent agent        varchar(255)    Mozilla/4.0 (compat; MSIE 6.0; Windows)
300 a       CGi request arguments   request_args    varchar(255)    user=Smith&cart=1231&item=532
301 b       Bytes transfered        bytes_sent      int unsigned    32561
302 c???    Text of cookie  cookie  varchar(255)    Apache=sdyn.fooonline.net 1300102700823
303 f       Local filename requested        request_file    varchar(255)    /var/www/html/books-cycroad.html
304 H       HTTP request_protocol   request_protocol        varchar(10)     HTTP/1.1
305 h       Name of remote host     remote_host     varchar(50)     blah.foobar.com
306 I       Request ID (from modd_unique_id)        id      char(19)        POlFcUBRH30AAALdBG8
307 l       Ident user info remote_logname  varcgar(50)     bobby
308 M       Machine ID???   machine_id      varchar(25)     web01
309 m       HTTP request method     request_method  varchar(10)     GET
310 P       httpd cchild PID        child_pid       smallint unsigned       3215
311 p       http port       server_port     smallint unsigned       80
312 R       Referer referer varchar(255)    http://www.biglinks4u.com/linkpage.html
313 r       Request in full form    request_line    varchar(255)    GET /books-cycroad.html HTTP/1.1
314 S       Time of request in UNIX time_t format   time_stamp      int unsigned    1005598029
315 T       Seconds to service request      request_duration        smallint unsigned       2
316 t       Time of request in human format request_time    char(28)        [02/Dec/2001:15:01:26 -0800]
317 U       Request in simple form  request_uri     varchar(255)    /books-cycroad.html
318 u       User info from HTTP auth        remote_user     varchar(50)     bobby
319 v       Virtual host servicing the request      virtual_host    varchar(255)
320         */
321         $dbh->genericSqlQuery("
322 CREATE TABLE $log_tbl (
323         time_stamp    int unsigned,
324         remote_host   varchar(50),
325         remote_user   varchar(50),
326         request_method varchar(10),
327         request_line  varchar(255),
328         request_args  varchar(255),
329         request_uri   varchar(255),
330         request_time  char(28),
331         status        smallint unsigned,
332         bytes_sent    smallint unsigned,
333         referer       varchar(255), 
334         agent         varchar(255),
335         request_duration float
336 )");
337         $dbh->genericSqlQuery("CREATE INDEX log_time ON $log_tbl (time_stamp)");
338         $dbh->genericSqlQuery("CREATE INDEX log_host ON $log_tbl (remote_host)");
339         echo "  ",_("CREATED");
340         break;
341     }
342     echo "<br />\n";
343 }
344
345 /**
346  * Update from ~1.3.4 to current.
347  * Only session, user, pref and member
348  * jeffs-hacks database api (around 1.3.2) later:
349  *   people should export/import their pages if using that old versions.
350  */
351 function CheckDatabaseUpdate(&$request) {
352     global $DBParams, $DBAuthParams;
353     if (!in_array($DBParams['dbtype'], array('SQL','ADODB'))) return;
354     echo "<h3>",_("check for necessary database updates"), " - ", $DBParams['dbtype'], "</h3>\n";
355
356     $dbh = $request->getDbh(); 
357     $dbadmin = $request->getArg('dbadmin');
358     _upgrade_db_init($dbh);
359     if (isset($dbadmin['cancel'])) {
360         echo _("CANCEL")," <br />\n";
361         return;
362     }
363
364     $tables = $dbh->_backend->listOfTables();
365     $backend_type = $dbh->_backend->backendType();
366     echo "<h4>",_("Backend type: "),$backend_type,"</h4>\n";
367     $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
368     foreach (explode(':','session:user:pref:member') as $table) {
369         echo sprintf(_("check for table %s"), $table)," ...";
370         if (!in_array($prefix.$table, $tables)) {
371             installTable($dbh, $table, $backend_type);
372         } else {
373             echo _("OK")," <br />\n";
374         }
375     }
376     if (ACCESS_LOG_SQL) {
377         $table = "accesslog";
378         echo sprintf(_("check for table %s"), $table)," ...";
379         if (!in_array($prefix.$table, $tables)) {
380             installTable($dbh, $table, $backend_type);
381         } else {
382             echo _("OK")," <br />\n";
383         }
384     }
385     if ((class_exists("RatingsUserFactory") or $dbh->isWikiPage(_("RateIt")))) {
386         $table = "rating";
387         echo sprintf(_("check for table %s"), $table)," ...";
388         if (!in_array($prefix.$table, $tables)) {
389             installTable($dbh, $table, $backend_type);
390         } else {
391             echo _("OK")," <br />\n";
392         }
393     }
394     $backend = &$dbh->_backend->_dbh;
395
396     // 1.3.8 added session.sess_ip
397     if (phpwiki_version() >= 1030.08 and USE_DB_SESSION and isset($request->_dbsession)) {
398         echo _("check for new session.sess_ip column")," ... ";
399         $database = $dbh->_backend->database();
400         assert(!empty($DBParams['db_session_table']));
401         $session_tbl = $prefix . $DBParams['db_session_table'];
402         $sess_fields = $dbh->_backend->listOfFields($database, $session_tbl);
403         if (!strstr(strtolower(join(':', $sess_fields)), "sess_ip")) {
404             // TODO: postgres test (should be able to add columns at the end, but not in between)
405             echo "<b>",_("ADDING"),"</b>"," ... ";              
406             $dbh->genericSqlQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
407             $dbh->genericSqlQuery("CREATE INDEX sess_date ON $session_tbl (sess_date)");
408         } else {
409             echo _("OK");
410         }
411         echo "<br />\n";
412         if (substr($backend_type,0,5) == 'mysql') {
413             // upgrade to 4.1.8 destroyed my session table: 
414             // sess_id => varchar(10), sess_data => varchar(5). For others obviously also.
415             echo _("check for mysql session.sess_id sanity")," ... ";
416             $result = $dbh->genericSqlQuery("DESCRIBE $session_tbl");
417             if ($DBParams['dbtype'] == 'SQL') {
418                 $iter = new WikiDB_backend_PearDB_generic_iter($backend, $result);
419             } else {
420                 $iter = new WikiDB_backend_ADODB_generic_iter($backend, $result, 
421                                 array("Field", "Type", "Null", "Key", "Default", "Extra"));
422             }
423             while ($col = $iter->next()) {
424                 if ($col["Field"] == 'sess_id' and !strstr(strtolower($col["Type"]), 'char(32)')) {
425                     $dbh->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_id sess_id CHAR(32) NOT NULL");
426                     echo "sess_id ", $col["Type"], " ", _("fixed"), " =&gt; CHAR(32) ";
427                 }
428                 if ($col["Field"] == 'sess_ip' and !strstr(strtolower($col["Type"]), 'char(15)')) {
429                     $dbh->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_ip sess_ip CHAR(15) NOT NULL");
430                     echo "sess_ip ", $col["Type"], " ", _("fixed"), " =&gt; CHAR(15) ";
431                 }
432             }
433             echo "<br />\n";
434         }
435     }
436
437     // mysql >= 4.0.4 requires LOCK TABLE privileges
438     if (substr($backend_type,0,5) == 'mysql') {
439         echo _("check for mysql LOCK TABLE privilege")," ...";
440         $mysql_version = $dbh->_backend->_serverinfo['version'];
441         if ($mysql_version > 400.40) {
442             if (function_exists('parseDSN')) // ADODB
443                 $parseDSN = parseDSN($DBParams['dsn']);
444             else // pear
445                 $parseDSN = DB::parseDSN($DBParams['dsn']);
446             $username = $dbh->_backend->qstr($parseDSN['username']);
447             // on db level
448             $query = "SELECT lock_tables_priv FROM mysql.db WHERE user='$username'";
449             //mysql_select_db("mysql", $dbh->_backend->connection());
450             $db_fields = $dbh->_backend->listOfFields("mysql", "db");
451             if (!strstr(strtolower(join(':', $db_fields)), "lock_tables_priv")) {
452                 die("lock_tables_priv missing. The DB Admin must run mysql_fix_privilege_tables");
453             }
454             $row = $backend->getRow($query);
455             if ($row and $row[0] == 'N') {
456                 $dbh->genericSqlQuery("UPDATE mysql.db SET lock_tables_priv='Y' WHERE mysql.user='$username'");
457                 $dbh->genericSqlQuery("FLUSH PRIVILEGES");
458                 echo "mysql.db user='$username'", _("fixed"), "<br />\n";
459             } elseif (!$row) {
460                 // or on user level
461                 $query = "SELECT lock_tables_priv FROM mysql.user WHERE user='$username'";
462                 $row = $backend->getRow($query);
463                 if ($row and $row[0] == 'N') {
464                     $dbh->genericSqlQuery("UPDATE mysql.user SET lock_tables_priv='Y' WHERE mysql.user='$username'");
465                     $dbh->genericSqlQuery("FLUSH PRIVILEGES");
466                     echo "mysql.user user='$username'", _("fixed"), "<br />\n";
467                 } elseif (!$row) {
468                     echo " <b><font color=\"red\">", _("FAILED"), "</font></b>: ",
469                         "Neither mysql.db nor mysql.user has a user='$username' or the lock_tables_priv field",
470                         "<br />\n";
471                 } else {
472                     echo _("OK"), "<br />\n";
473                 }
474             } else {
475                 echo _("OK"), "<br />\n";
476             }
477             //mysql_select_db($dbh->_backend->database(), $dbh->_backend->connection());
478         } else {
479             echo sprintf(_("version <em>%s</em> not affected"), $mysql_version),"<br />\n";
480         }
481     }
482
483     // 1.3.10 mysql requires page.id auto_increment
484     // mysql, mysqli or mysqlt
485     if (phpwiki_version() >= 1030.099 and substr($backend_type,0,5) == 'mysql') {
486         echo _("check for mysql page.id auto_increment flag")," ...";
487         extract($dbh->_backend->_table_names);
488         assert(!empty($page_tbl));
489         $database = $dbh->_backend->database();
490         $fields = mysql_list_fields($database, $page_tbl, $dbh->_backend->connection());
491         $columns = mysql_num_fields($fields); 
492         for ($i = 0; $i < $columns; $i++) {
493             if (mysql_field_name($fields, $i) == 'id') {
494                 $flags = mysql_field_flags($fields, $i);
495                 //DONE: something was wrong with ADODB here.
496                 if (!strstr(strtolower($flags), "auto_increment")) {
497                     echo "<b>",_("ADDING"),"</b>"," ... ";
498                     // MODIFY col_def valid since mysql 3.22.16,
499                     // older mysql's need CHANGE old_col col_def
500                     $dbh->genericSqlQuery("ALTER TABLE $page_tbl CHANGE id id INT NOT NULL AUTO_INCREMENT");
501                     $fields = mysql_list_fields($database, $page_tbl);
502                     if (!strstr(strtolower(mysql_field_flags($fields, $i)), "auto_increment"))
503                         echo " <b><font color=\"red\">", _("FAILED"), "</font></b><br />\n";
504                     else     
505                         echo _("OK"), "<br />\n";
506                 } else {
507                     echo _("OK"), "<br />\n";
508                 }
509                 break;
510             }
511         }
512         mysql_free_result($fields);
513     }
514
515     // Check for mysql 4.1.x/5.0.0a binary search problem.
516     //   http://bugs.mysql.com/bug.php?id=4398
517     // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
518     // Confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
519     // On windows only.
520     if (isWindows() and substr($backend_type,0,5) == 'mysql') {
521         echo _("check for mysql 4.1.x/5.0.0 binary search on windows problem")," ...";
522         $mysql_version = $dbh->_backend->_serverinfo['version'];
523         if ($mysql_version < 401.0) { 
524             echo sprintf(_("version <em>%s</em> not affected"), $mysql_version),"<br />\n";
525         } elseif ($mysql_version >= 401.6)  {
526             $row = $backend->getRow("SHOW CREATE TABLE $page_tbl");
527             $result = join(" ", $row);
528             if (strstr(strtolower($result), "character set") 
529                 and strstr(strtolower($result), "collate")) 
530             {
531                 echo _("OK"), "<br />\n";
532             } else {
533                 $dbh->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename "
534                                      ."pagename VARCHAR(100) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL");
535                 echo sprintf(_("version <em>%s</em> <b>FIXED</b>"), $mysql_version),"<br />\n";
536             }
537         } else {
538             // check if already fixed
539             extract($dbh->_backend->_table_names);
540             assert(!empty($page_tbl));
541             $database = $dbh->_backend->database();
542             $fields = mysql_list_fields($database, $page_tbl, $dbh->_backend->connection());
543             $columns = mysql_num_fields($fields); 
544             for ($i = 0; $i < $columns; $i++) {
545                 if (mysql_field_name($fields, $i) == 'pagename') {
546                     $flags = mysql_field_flags($fields, $i);
547                     // I think it was fixed with 4.1.6, but I tested it only with 4.1.8
548                     if ($mysql_version > 401.0 and $mysql_version < 401.6) { 
549                         // remove the binary flag
550                         if (strstr(strtolower($flags), "binary")) {
551                             // FIXME: on duplicate pagenames this will fail!
552                             $dbh->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename pagename VARCHAR(100) NOT NULL");
553                             echo sprintf(_("version <em>%s</em> <b>FIXED</b>"), $mysql_version),"<br />\n";     
554                         }
555                     }
556                     break;
557                 }
558             }
559         }
560     }
561
562     _upgrade_cached_html($dbh);
563
564     return;
565 }
566
567 function _upgrade_db_init (&$dbh) {
568     global $request, $DBParams, $DBAuthParams;
569     if (!in_array($DBParams['dbtype'], array('SQL','ADODB'))) return;
570
571     if (DBADMIN_USER) {
572         // if need to connect as the root user, for CREATE and ALTER privileges
573         $AdminParams = $DBParams;
574         if ($DBParams['dbtype'] == 'SQL')
575             $dsn = DB::parseDSN($AdminParams['dsn']);
576         else
577             $dsn = parseDSN($AdminParams['dsn']);
578         $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
579                                       $dsn['phptype'],
580                                       DBADMIN_USER,
581                                       DBADMIN_PASSWD,
582                                       $dsn['hostspec'],
583                                       $dsn['database']);
584         $dbh = WikiDB::open($AdminParams);
585     } elseif ($dbadmin = $request->getArg('dbadmin')) {
586         if (empty($dbadmin['user']) or isset($dbadmin['cancel']))
587             $dbh = &$request->_dbi;
588         else {
589             $AdminParams = $DBParams;
590             if ($DBParams['dbtype'] == 'SQL')
591                 $dsn = DB::parseDSN($AdminParams['dsn']);
592             else
593                 $dsn = parseDSN($AdminParams['dsn']);
594             $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
595                                       $dsn['phptype'],
596                                       $dbadmin['user'],
597                                       $dbadmin['passwd'],
598                                       $dsn['hostspec'],
599                                       $dsn['database']);
600             $dbh = WikiDB::open($AdminParams);
601         }
602     } else {
603         // Check if the privileges are enough. Need CREATE and ALTER perms. 
604         // And on windows: SELECT FROM mysql, possibly: UPDATE mysql.
605         $form = HTML::form(array("method" => "post", 
606                                  "action" => $request->getPostURL(),
607                                  "accept-charset"=>$GLOBALS['charset']),
608                            HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."),
609                                    HTML::br(),
610                                    _("And on windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")),
611                            HiddenInputs(array('action' => 'upgrade')),
612                            HTML::table(array("cellspacing"=>4),
613                                        HTML::tr(HTML::td(array('align'=>'right'),
614                                                          _("DB admin user:")),
615                                                 HTML::td(HTML::input(array('name'=>"dbadmin[user]",
616                                                                            'size'=>12,
617                                                                            'maxlength'=>256,
618                                                                            'value'=>'root')))),
619                                        HTML::tr(HTML::td(array('align'=>'right'),
620                                                          _("DB admin password:")),
621                                                 HTML::td(HTML::input(array('name'=>"dbadmin[passwd]",
622                                                                            'type'=>'password',
623                                                                            'size'=>12,
624                                                                            'maxlength'=>256)))),
625                                        HTML::tr(HTML::td(array('align'=>'center', 'colspan' => 2),
626                                                          Button("submit:", _("Submit"), 'wikiaction'), 
627                                                          HTML::raw('&nbsp;'),
628                                                          Button("submit:dbadmin[cancel]", _("Cancel"), 'button')))));
629         $form->printXml();
630         echo "</div><!-- content -->\n";
631         echo asXML(Template("bottom"));
632         echo "</body></html>\n";
633         $request->finish();
634         exit();
635     }
636 }
637
638 /**
639  * if page.cached_html does not exists:
640  *   put _cached_html from pagedata into a new seperate blob, not huge serialized string.
641  *
642  * it is only rarelely needed: for current page only, if-not-modified
643  * but was extracetd for every simple page iteration.
644  */
645 function _upgrade_cached_html (&$dbh, $verbose=true) {
646     global $DBParams;
647     if (!in_array($DBParams['dbtype'], array('SQL','ADODB'))) return;
648     $count = 0;
649     if (phpwiki_version() >= 1030.10) {
650         if ($verbose)
651             echo _("check for extra page.cached_html column")," ... ";
652         $database = $dbh->_backend->database();
653         extract($dbh->_backend->_table_names);
654         $fields = $dbh->_backend->listOfFields($database, $page_tbl);
655         if (!strstr(strtolower(join(':', $fields)), "cached_html")) {
656             if ($verbose)
657                 echo "<b>",_("ADDING"),"</b>"," ... ";
658             $backend_type = $dbh->_backend->backendType();
659             if (substr($backend_type,0,5) == 'mysql')
660                 $dbh->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB");
661             else
662                 $dbh->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB");
663             if ($verbose)
664                 echo "<b>",_("CONVERTING"),"</b>"," ... ";
665             $count = _convert_cached_html($dbh);
666             if ($verbose)
667                 echo $count, " ", _("OK"), "<br />\n";
668         } else {
669             if ($verbose)
670                 echo _("OK"), "<br />\n";
671         }
672     }
673     return $count;
674 }
675
676 /** 
677  * move _cached_html for all pages from pagedata into a new seperate blob.
678  * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also.
679  */
680 function _convert_cached_html (&$dbh) {
681     global $DBParams;
682     if (!in_array($DBParams['dbtype'], array('SQL','ADODB'))) return;
683
684     $pages = $dbh->getAllPages();
685     $cache =& $dbh->_cache;
686     $count = 0;
687     extract($dbh->_backend->_table_names);
688     while ($page = $pages->next()) {
689         $pagename = $page->getName();
690         $data = $dbh->_backend->get_pagedata($pagename);
691         if (!empty($data['_cached_html'])) {
692             $cached_html = $data['_cached_html'];
693             $data['_cached_html'] = '';
694             $cache->update_pagedata($pagename, $data);
695             // store as blob, not serialized
696             $dbh->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?",
697                                   array($cached_html, $pagename));
698             $count++;
699         }
700     }
701     return $count;
702 }
703
704 function CheckPluginUpdate(&$request) {
705     echo "<h3>",_("check for necessary plugin argument updates"),"</h3>\n";
706     $process = array('msg' => _("change RandomPage pages => numpages"),
707                      'match' => "/(<\?\s*plugin\s+ RandomPage\s+)pages/",
708                      'replace' => "\\1numpages");
709     $dbi = $request->getDbh();
710     $allpages = $dbi->getAllPages(false);
711     while ($page = $allpages->next()) {
712         $current = $page->getCurrentRevision();
713         $pagetext = $current->getPackedContent();
714         foreach ($process as $p) {
715             if (preg_match($p['match'], $pagetext)) {
716                 echo $page->getName()," ",$p['msg']," ... ";
717                 if ($newtext = preg_replace($p['match'], $p['replace'], $pagetext)) {
718                     $meta = $current->_data;
719                     $meta['summary'] = "upgrade: ".$p['msg'];
720                     $page->save($newtext, $current->getVersion() + 1, $meta);
721                     echo _("OK"), "<br />\n";
722                 } else {
723                     echo " <b><font color=\"red\">", _("FAILED"), "</font></b><br />\n";
724                 }
725             }
726         }
727     }
728 }
729
730 function fixConfigIni($match, $new) {
731     $file = FindFile("config/config.ini");
732     $found = false;
733     if (is_writable($file)) {
734         $in = fopen($file,"rb");
735         $out = fopen($tmp = tempnam(FindFile("uploads"),"cfg"),"wb");
736         if (isWindows())
737             $tmp = str_replace("/","\\",$tmp);
738         while ($s = fgets($in)) {
739             if (preg_match($match, $s)) {
740                 $s = $new . (isWindows() ? "\r\n" : "\n");
741                 $found = true;
742             }
743             fputs($out, $s);
744         }
745         fclose($in);
746         fclose($out);
747         if (!$found) {
748             echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
749                 sprintf(_("%s not found"), $match);
750             unlink($out);
751         } else {
752             @unlink("$file.bak");
753             @rename($file,"$file.bak");
754             if (rename($tmp, $file))
755                 echo " <b>",_("FIXED"),"</b>";
756             else {
757                 echo " <b>",_("FAILED"),"</b>: ";
758                 sprintf(_("couldn't move %s to %s"), $tmp, $file);
759                 return false;
760             }
761         }
762         return $found;
763     } else {
764         echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
765             sprintf(_("%s is not writable"), $file);
766         return false;
767     }
768 }
769
770 function CheckConfigUpdate(&$request) {
771     echo "<h3>",_("check for necessary config updates"),"</h3>\n";
772     echo _("check for old CACHE_CONTROL = NONE")," ... ";
773     if (defined('CACHE_CONTROL') and CACHE_CONTROL == '') {
774         echo "<br />&nbsp;&nbsp;",_("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'")," ...";
775         fixConfigIni("/^\s*CACHE_CONTROL\s*=\s*NONE/","CACHE_CONTROL = NO_CACHE");
776     } else {
777         echo _("OK");
778     }
779     echo "<br />\n";
780     echo _("check for GROUP_METHOD = NONE")," ... ";
781     if (defined('GROUP_METHOD') and GROUP_METHOD == '') {
782         echo "<br />&nbsp;&nbsp;",_("GROUP_METHOD is set to NONE, and must be changed to \"NONE\"")," ...";
783         fixConfigIni("/^\s*GROUP_METHOD\s*=\s*NONE/","GROUP_METHOD = \"NONE\"");
784     } else {
785         echo _("OK");
786     }
787     echo "<br />\n";
788 }
789
790 /**
791  * TODO:
792  *
793  * Upgrade: Base class for multipage worksteps
794  * identify, validate, display options, next step
795  */
796 /*
797 class Upgrade {
798 }
799
800 class Upgrade_CheckPgsrc extends Upgrade {
801 }
802
803 class Upgrade_CheckDatabaseUpdate extends Upgrade {
804 }
805 */
806
807 // TODO: At which step are we? 
808 // validate and do it again or go on with next step.
809
810 /** entry function from lib/main.php
811  */
812 function DoUpgrade($request) {
813
814     if (!$request->_user->isAdmin()) {
815         $request->_notAuthorized(WIKIAUTH_ADMIN);
816         $request->finish(
817                          HTML::div(array('class' => 'disabled-plugin'),
818                                    fmt("Upgrade disabled: user != isAdmin")));
819         return;
820     }
821
822     StartLoadDump($request, _("Upgrading this PhpWiki"));
823     //CheckOldIndexUpdate($request); // to upgrade from < 1.3.10
824     CheckDatabaseUpdate($request);   // first check cached_html and friends
825     CheckActionPageUpdate($request);
826     CheckPgsrcUpdate($request);
827     //CheckThemeUpdate($request);
828     //CheckPluginUpdate($request);
829     CheckConfigUpdate($request);
830     EndLoadDump($request);
831 }
832
833
834 /*
835  $Log: not supported by cvs2svn $
836  Revision 1.39  2005/01/30 23:09:17  rurban
837  sanify session fields
838
839  Revision 1.38  2005/01/25 07:57:02  rurban
840  add dbadmin form, add mysql LOCK TABLES check, add plugin args updater (not yet activated)
841
842  Revision 1.37  2005/01/20 10:19:08  rurban
843  add InterWikiMap to special pages
844
845  Revision 1.36  2004/12/20 12:56:11  rurban
846  patch #1088128 by Kai Krakow. avoid chicken & egg problem
847
848  Revision 1.35  2004/12/13 14:35:41  rurban
849  verbose arg
850
851  Revision 1.34  2004/12/11 09:39:28  rurban
852  needed init for ref
853
854  Revision 1.33  2004/12/10 22:33:39  rurban
855  add WikiAdminUtils method for convert-cached-html
856  missed some vars.
857
858  Revision 1.32  2004/12/10 22:15:00  rurban
859  fix $page->get('_cached_html)
860  refactor upgrade db helper _convert_cached_html() to be able to call them from WikiAdminUtils also.
861  support 2nd genericSqlQuery param (bind huge arg)
862
863  Revision 1.31  2004/12/10 02:45:26  rurban
864  SQL optimization:
865    put _cached_html from pagedata into a new seperate blob, not huge serialized string.
866    it is only rarelely needed: for current page only, if-not-modified
867    but was extracted for every simple page iteration.
868
869  Revision 1.30  2004/11/29 17:58:57  rurban
870  just aesthetics
871
872  Revision 1.29  2004/11/29 16:08:31  rurban
873  added missing nl
874
875  Revision 1.28  2004/11/16 16:25:14  rurban
876  fix accesslog tablename, print CREATED only if really done
877
878  Revision 1.27  2004/11/07 16:02:52  rurban
879  new sql access log (for spam prevention), and restructured access log class
880  dbh->quote (generic)
881  pear_db: mysql specific parts seperated (using replace)
882
883  Revision 1.26  2004/10/14 19:19:34  rurban
884  loadsave: check if the dumped file will be accessible from outside.
885  and some other minor fixes. (cvsclient native not yet ready)
886
887  Revision 1.25  2004/09/06 08:28:00  rurban
888  rename genericQuery to genericSqlQuery
889
890  Revision 1.24  2004/07/05 13:56:22  rurban
891  sqlite autoincrement fix
892
893  Revision 1.23  2004/07/04 10:28:06  rurban
894  DBADMIN_USER fix
895
896  Revision 1.22  2004/07/03 17:21:28  rurban
897  updated docs: submitted new mysql bugreport (#1491 did not fix it)
898
899  Revision 1.21  2004/07/03 16:51:05  rurban
900  optional DBADMIN_USER:DBADMIN_PASSWD for action=upgrade (if no ALTER permission)
901  added atomic mysql REPLACE for PearDB as in ADODB
902  fixed _lock_tables typo links => link
903  fixes unserialize ADODB bug in line 180
904
905  Revision 1.20  2004/07/03 14:48:18  rurban
906  Tested new mysql 4.1.3-beta: binary search bug as fixed.
907  => fixed action=upgrade,
908  => version check in PearDB also (as in ADODB)
909
910  Revision 1.19  2004/06/19 12:19:09  rurban
911  slightly improved docs
912
913  Revision 1.18  2004/06/19 11:47:17  rurban
914  added CheckConfigUpdate: CACHE_CONTROL = NONE => NO_CACHE
915
916  Revision 1.17  2004/06/17 11:31:50  rurban
917  check necessary localized actionpages
918
919  Revision 1.16  2004/06/16 10:38:58  rurban
920  Disallow refernces in calls if the declaration is a reference
921  ("allow_call_time_pass_reference clean").
922    PhpWiki is now allow_call_time_pass_reference = Off clean,
923    but several external libraries may not.
924    In detail these libs look to be affected (not tested):
925    * Pear_DB odbc
926    * adodb oracle
927
928  Revision 1.15  2004/06/07 19:50:40  rurban
929  add owner field to mimified dump
930
931  Revision 1.14  2004/06/07 18:38:18  rurban
932  added mysql 4.1.x search fix
933
934  Revision 1.13  2004/06/04 20:32:53  rurban
935  Several locale related improvements suggested by Pierrick Meignen
936  LDAP fix by John Cole
937  reanable admin check without ENABLE_PAGEPERM in the admin plugins
938
939  Revision 1.12  2004/05/18 13:59:15  rurban
940  rename simpleQuery to genericSqlQuery
941
942  Revision 1.11  2004/05/15 13:06:17  rurban
943  skip the HomePage, at first upgrade the ActionPages, then the database, then the rest
944
945  Revision 1.10  2004/05/15 01:19:41  rurban
946  upgrade prefix fix by Kai Krakow
947
948  Revision 1.9  2004/05/14 11:33:03  rurban
949  version updated to 1.3.11pre
950  upgrade stability fix
951
952  Revision 1.8  2004/05/12 10:49:55  rurban
953  require_once fix for those libs which are loaded before FileFinder and
954    its automatic include_path fix, and where require_once doesn't grok
955    dirname(__FILE__) != './lib'
956  upgrade fix with PearDB
957  navbar.tmpl: remove spaces for IE &nbsp; button alignment
958
959  Revision 1.7  2004/05/06 17:30:38  rurban
960  CategoryGroup: oops, dos2unix eol
961  improved phpwiki_version:
962    pre -= .0001 (1.3.10pre: 1030.099)
963    -p1 += .001 (1.3.9-p1: 1030.091)
964  improved InstallTable for mysql and generic SQL versions and all newer tables so far.
965  abstracted more ADODB/PearDB methods for action=upgrade stuff:
966    backend->backendType(), backend->database(),
967    backend->listOfFields(),
968    backend->listOfTables(),
969
970  Revision 1.6  2004/05/03 15:05:36  rurban
971  + table messages
972
973  Revision 1.4  2004/05/02 21:26:38  rurban
974  limit user session data (HomePageHandle and auth_dbi have to invalidated anyway)
975    because they will not survive db sessions, if too large.
976  extended action=upgrade
977  some WikiTranslation button work
978  revert WIKIAUTH_UNOBTAINABLE (need it for main.php)
979  some temp. session debug statements
980
981  Revision 1.3  2004/04/29 22:33:30  rurban
982  fixed sf.net bug #943366 (Kai Krakow)
983    couldn't load localized url-undecoded pagenames
984
985  Revision 1.2  2004/03/12 15:48:07  rurban
986  fixed explodePageList: wrong sortby argument order in UnfoldSubpages
987  simplified lib/stdlib.php:explodePageList
988
989  */
990
991 // For emacs users
992 // Local Variables:
993 // mode: php
994 // tab-width: 8
995 // c-basic-offset: 4
996 // c-hanging-comment-ender-p: nil
997 // indent-tabs-mode: nil
998 // End:
999 ?>