]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/upgrade.php
keep overwrite arg
[SourceForge/phpwiki.git] / lib / upgrade.php
1 <?php //-*-php-*-
2 rcs_id('$Id: upgrade.php,v 1.50 2006-06-18 11:04:09 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 (!$dbh->_backend->isSQL()) 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"),
178         "schemas/${backend_type}.sql")," ... <br />\n";
179         return false;
180     }
181     */
182     extract($dbh->_backend->_table_names);
183     $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
184     switch ($table) {
185     case 'session':
186         assert($session_tbl);
187         if ($backend_type == 'mysql') {
188             $dbh->genericSqlQuery("
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),
195         INDEX (sess_date)
196 )");
197         } else {
198             $dbh->genericSqlQuery("
199 CREATE TABLE $session_tbl (
200         sess_id         CHAR(32) NOT NULL DEFAULT '',
201         sess_data       ".($backend_type == 'pgsql'?'TEXT':'BLOB')." NOT NULL,
202         sess_date       INT,
203         sess_ip         CHAR(15) NOT NULL
204 )");
205             $dbh->genericSqlQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
206         }
207         $dbh->genericSqlQuery("CREATE INDEX sess_date on session (sess_date)");
208         echo "  ",_("CREATED");
209         break;
210     case 'user':
211         $user_tbl = $prefix.'user';
212         if ($backend_type == 'mysql') {
213             $dbh->genericSqlQuery("
214 CREATE TABLE $user_tbl (
215         userid  CHAR(48) BINARY NOT NULL UNIQUE,
216         passwd  CHAR(48) BINARY DEFAULT '',
217         PRIMARY KEY (userid)
218 )");
219         } else {
220             $dbh->genericSqlQuery("
221 CREATE TABLE $user_tbl (
222         userid  CHAR(48) NOT NULL,
223         passwd  CHAR(48) DEFAULT ''
224 )");
225             $dbh->genericSqlQuery("CREATE UNIQUE INDEX userid ON $user_tbl (userid)");
226         }
227         echo "  ",_("CREATED");
228         break;
229     case 'pref':
230         $pref_tbl = $prefix.'pref';
231         if ($backend_type == 'mysql') {
232             $dbh->genericSqlQuery("
233 CREATE TABLE $pref_tbl (
234         userid  CHAR(48) BINARY NOT NULL UNIQUE,
235         prefs   TEXT NULL DEFAULT '',
236         PRIMARY KEY (userid)
237 )");
238         } else {
239             $dbh->genericSqlQuery("
240 CREATE TABLE $pref_tbl (
241         userid  CHAR(48) NOT NULL,
242         prefs   TEXT NULL DEFAULT '',
243 )");
244             $dbh->genericSqlQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
245         }
246         echo "  ",_("CREATED");
247         break;
248     case 'member':
249         $member_tbl = $prefix.'member';
250         if ($backend_type == 'mysql') {
251             $dbh->genericSqlQuery("
252 CREATE TABLE $member_tbl (
253         userid    CHAR(48) BINARY NOT NULL,
254         groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
255         INDEX (userid),
256         INDEX (groupname)
257 )");
258         } else {
259             $dbh->genericSqlQuery("
260 CREATE TABLE $member_tbl (
261         userid    CHAR(48) NOT NULL,
262         groupname CHAR(48) NOT NULL DEFAULT 'users',
263 )");
264             $dbh->genericSqlQuery("CREATE INDEX userid ON $member_tbl (userid)");
265             $dbh->genericSqlQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
266         }
267         echo "  ",_("CREATED");
268         break;
269     case 'rating':
270         $rating_tbl = $prefix.'rating';
271         if ($backend_type == 'mysql') {
272             $dbh->genericSqlQuery("
273 CREATE TABLE $rating_tbl (
274         dimension INT(4) NOT NULL,
275         raterpage INT(11) NOT NULL,
276         rateepage INT(11) NOT NULL,
277         ratingvalue FLOAT NOT NULL,
278         rateeversion INT(11) NOT NULL,
279         tstamp TIMESTAMP(14) NOT NULL,
280         PRIMARY KEY (dimension, raterpage, rateepage)
281 )");
282         } else {
283             $dbh->genericSqlQuery("
284 CREATE TABLE $rating_tbl (
285         dimension INT(4) NOT NULL,
286         raterpage INT(11) NOT NULL,
287         rateepage INT(11) NOT NULL,
288         ratingvalue FLOAT NOT NULL,
289         rateeversion INT(11) NOT NULL,
290         tstamp TIMESTAMP(14) NOT NULL,
291 )");
292             $dbh->genericSqlQuery("CREATE UNIQUE INDEX rating"
293                                   ." ON $rating_tbl (dimension, raterpage, rateepage)");
294         }
295         echo "  ",_("CREATED");
296         break;
297     case 'accesslog':
298         $log_tbl = $prefix.'accesslog';
299         // fields according to http://www.outoforder.cc/projects/apache/mod_log_sql/docs-2.0/#id2756178
300         /*
301 A       User Agent agent        varchar(255)    Mozilla/4.0 (compat; MSIE 6.0; Windows)
302 a       CGi request arguments   request_args    varchar(255)    user=Smith&cart=1231&item=532
303 b       Bytes transfered        bytes_sent      int unsigned    32561
304 c???    Text of cookie  cookie  varchar(255)    Apache=sdyn.fooonline.net 1300102700823
305 f       Local filename requested        request_file    varchar(255)    /var/www/html/books-cycroad.html
306 H       HTTP request_protocol   request_protocol        varchar(10)     HTTP/1.1
307 h       Name of remote host     remote_host     varchar(50)     blah.foobar.com
308 I       Request ID (from modd_unique_id)        id      char(19)        POlFcUBRH30AAALdBG8
309 l       Ident user info remote_logname  varcgar(50)     bobby
310 M       Machine ID???   machine_id      varchar(25)     web01
311 m       HTTP request method     request_method  varchar(10)     GET
312 P       httpd cchild PID        child_pid       smallint unsigned       3215
313 p       http port       server_port     smallint unsigned       80
314 R       Referer referer varchar(255)    http://www.biglinks4u.com/linkpage.html
315 r       Request in full form    request_line    varchar(255)    GET /books-cycroad.html HTTP/1.1
316 S       Time of request in UNIX time_t format   time_stamp      int unsigned    1005598029
317 T       Seconds to service request      request_duration        smallint unsigned       2
318 t       Time of request in human format request_time    char(28)        [02/Dec/2001:15:01:26 -0800]
319 U       Request in simple form  request_uri     varchar(255)    /books-cycroad.html
320 u       User info from HTTP auth        remote_user     varchar(50)     bobby
321 v       Virtual host servicing the request      virtual_host    varchar(255)
322         */
323         $dbh->genericSqlQuery("
324 CREATE TABLE $log_tbl (
325         time_stamp    int unsigned,
326         remote_host   varchar(50),
327         remote_user   varchar(50),
328         request_method varchar(10),
329         request_line  varchar(255),
330         request_args  varchar(255),
331         request_uri   varchar(255),
332         request_time  char(28),
333         status        smallint unsigned,
334         bytes_sent    smallint unsigned,
335         referer       varchar(255), 
336         agent         varchar(255),
337         request_duration float
338 )");
339         $dbh->genericSqlQuery("CREATE INDEX log_time ON $log_tbl (time_stamp)");
340         $dbh->genericSqlQuery("CREATE INDEX log_host ON $log_tbl (remote_host)");
341         echo "  ",_("CREATED");
342         break;
343     }
344     echo "<br />\n";
345 }
346
347 /**
348  * Update from ~1.3.4 to current.
349  * Only session, user, pref and member
350  * jeffs-hacks database api (around 1.3.2) later:
351  *   people should export/import their pages if using that old versions.
352  */
353 function CheckDatabaseUpdate(&$request) {
354     global $DBAuthParams;
355     $dbh = $request->getDbh(); 
356     if (!$dbh->_backend->isSQL()) return;
357     echo "<h3>",_("check for necessary database updates"), " - ", DATABASE_TYPE, "</h3>\n";
358
359     $dbadmin = $request->getArg('dbadmin');
360     _upgrade_db_init($dbh);
361     if (isset($dbadmin['cancel'])) {
362         echo _("CANCEL")," <br />\n";
363         return;
364     }
365
366     $tables = $dbh->_backend->listOfTables();
367     $backend_type = $dbh->_backend->backendType();
368     echo "<h4>",_("Backend type: "),$backend_type,"</h4>\n";
369     $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
370     foreach (explode(':','session:pref:member') as $table) {
371         echo sprintf(_("check for table %s"), $table)," ...";
372         if (!in_array($prefix.$table, $tables)) {
373             installTable($dbh, $table, $backend_type);
374         } else {
375             echo _("OK")," <br />\n";
376         }
377     }
378     if (ACCESS_LOG_SQL) {
379         $table = "accesslog";
380         echo sprintf(_("check for table %s"), $table)," ...";
381         if (!in_array($prefix.$table, $tables)) {
382             installTable($dbh, $table, $backend_type);
383         } else {
384             echo _("OK")," <br />\n";
385         }
386     }
387     if ((class_exists("RatingsUserFactory") or $dbh->isWikiPage(_("RateIt")))) {
388         $table = "rating";
389         echo sprintf(_("check for table %s"), $table)," ...";
390         if (!in_array($prefix.$table, $tables)) {
391             installTable($dbh, $table, $backend_type);
392         } else {
393             echo _("OK")," <br />\n";
394         }
395     }
396     $backend = &$dbh->_backend->_dbh;
397     extract($dbh->_backend->_table_names);
398
399     // 1.3.8 added session.sess_ip
400     if (phpwiki_version() >= 1030.08 and USE_DB_SESSION and isset($request->_dbsession)) {
401         echo _("check for new session.sess_ip column")," ... ";
402         $database = $dbh->_backend->database();
403         assert(!empty($DBParams['db_session_table']));
404         $session_tbl = $prefix . $DBParams['db_session_table'];
405         $sess_fields = $dbh->_backend->listOfFields($database, $session_tbl);
406         if (!$sess_fields) {
407             echo _("SKIP");
408         } elseif (!strstr(strtolower(join(':', $sess_fields)), "sess_ip")) {
409             // TODO: postgres test (should be able to add columns at the end, but not in between)
410             echo "<b>",_("ADDING"),"</b>"," ... ";              
411             $dbh->genericSqlQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
412             $dbh->genericSqlQuery("CREATE INDEX sess_date ON $session_tbl (sess_date)");
413         } else {
414             echo _("OK");
415         }
416         echo "<br />\n";
417         if (substr($backend_type,0,5) == 'mysql') {
418             // upgrade to 4.1.8 destroyed my session table: 
419             // sess_id => varchar(10), sess_data => varchar(5). For others obviously also.
420             echo _("check for mysql session.sess_id sanity")," ... ";
421             $result = $dbh->genericSqlQuery("DESCRIBE $session_tbl");
422             if (DATABASE_TYPE == 'SQL') {
423                 $iter = new WikiDB_backend_PearDB_generic_iter($backend, $result);
424             } elseif (DATABASE_TYPE == 'ADODB') {
425                 $iter = new WikiDB_backend_ADODB_generic_iter($backend, $result, 
426                                 array("Field", "Type", "Null", "Key", "Default", "Extra"));
427             } elseif (DATABASE_TYPE == 'PDO') {
428                 $iter = new WikiDB_backend_PDO_generic_iter($backend, $result);
429             }
430             while ($col = $iter->next()) {
431                 if ($col["Field"] == 'sess_id' and !strstr(strtolower($col["Type"]), 'char(32)')) {
432                     $dbh->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_id"
433                                           ." sess_id CHAR(32) NOT NULL");
434                     echo "sess_id ", $col["Type"], " ", _("fixed"), " =&gt; CHAR(32) ";
435                 }
436                 if ($col["Field"] == 'sess_ip' and !strstr(strtolower($col["Type"]), 'char(15)')) {
437                     $dbh->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_ip"
438                                           ." sess_ip CHAR(15) NOT NULL");
439                     echo "sess_ip ", $col["Type"], " ", _("fixed"), " =&gt; CHAR(15) ";
440                 }
441             }
442             echo _("OK"), "<br />\n";
443         }
444     }
445
446     /* TODO:
447      ALTER TABLE link ADD relation INT DEFAULT 0;
448      CREATE INDEX linkrelation ON link (relation);
449     */
450
451     // mysql >= 4.0.4 requires LOCK TABLE privileges
452     if (0 and substr($backend_type,0,5) == 'mysql') {
453         echo _("check for mysql LOCK TABLE privilege")," ...";
454         $mysql_version = $dbh->_backend->_serverinfo['version'];
455         if ($mysql_version > 400.40) {
456             if (!empty($dbh->_backend->_parsedDSN))
457                 $parseDSN = $dbh->_backend->_parsedDSN;
458             elseif (function_exists('parseDSN')) // ADODB or PDO
459                 $parseDSN = parseDSN($DBParams['dsn']);
460             else                             // pear
461                 $parseDSN = DB::parseDSN($DBParams['dsn']);
462             $username = $dbh->_backend->qstr($parseDSN['username']);
463             // on db level
464             $query = "SELECT lock_tables_priv FROM mysql.db WHERE user='$username'";
465             //mysql_select_db("mysql", $dbh->_backend->connection());
466             $db_fields = $dbh->_backend->listOfFields("mysql", "db");
467             if (!strstr(strtolower(join(':', $db_fields)), "lock_tables_priv")) {
468                 echo join(':', $db_fields);
469                 die("lock_tables_priv missing. The DB Admin must run mysql_fix_privilege_tables");
470             }
471             $row = $dbh->_backend->getRow($query);
472             if (isset($row[0]) and $row[0] == 'N') {
473                 $dbh->genericSqlQuery("UPDATE mysql.db SET lock_tables_priv='Y'"
474                                       ." WHERE mysql.user='$username'");
475                 $dbh->genericSqlQuery("FLUSH PRIVILEGES");
476                 echo "mysql.db user='$username'", _("fixed"), "<br />\n";
477             } elseif (!$row) {
478                 // or on user level
479                 $query = "SELECT lock_tables_priv FROM mysql.user WHERE user='$username'";
480                 $row = $dbh->_backend->getRow($query);
481                 if ($row and $row[0] == 'N') {
482                     $dbh->genericSqlQuery("UPDATE mysql.user SET lock_tables_priv='Y'"
483                                           ." WHERE mysql.user='$username'");
484                     $dbh->genericSqlQuery("FLUSH PRIVILEGES");
485                     echo "mysql.user user='$username'", _("fixed"), "<br />\n";
486                 } elseif (!$row) {
487                     echo " <b><font color=\"red\">", _("FAILED"), "</font></b>: ",
488                         "Neither mysql.db nor mysql.user has a user='$username'"
489                         ." or the lock_tables_priv field",
490                         "<br />\n";
491                 } else {
492                     echo _("OK"), "<br />\n";
493                 }
494             } else {
495                 echo _("OK"), "<br />\n";
496             }
497             //mysql_select_db($dbh->_backend->database(), $dbh->_backend->connection());
498         } else {
499             echo sprintf(_("version <em>%s</em> not affected"), $mysql_version),"<br />\n";
500         }
501     }
502
503     // 1.3.10 mysql requires page.id auto_increment
504     // mysql, mysqli or mysqlt
505     if (phpwiki_version() >= 1030.099 and substr($backend_type,0,5) == 'mysql' 
506         and DATABASE_TYPE != 'PDO') {
507         echo _("check for mysql page.id auto_increment flag")," ...";
508         assert(!empty($page_tbl));
509         $database = $dbh->_backend->database();
510         $fields = mysql_list_fields($database, $page_tbl, $dbh->_backend->connection());
511         $columns = mysql_num_fields($fields); 
512         for ($i = 0; $i < $columns; $i++) {
513             if (mysql_field_name($fields, $i) == 'id') {
514                 $flags = mysql_field_flags($fields, $i);
515                 //DONE: something was wrong with ADODB here.
516                 if (!strstr(strtolower($flags), "auto_increment")) {
517                     echo "<b>",_("ADDING"),"</b>"," ... ";
518                     // MODIFY col_def valid since mysql 3.22.16,
519                     // older mysql's need CHANGE old_col col_def
520                     $dbh->genericSqlQuery("ALTER TABLE $page_tbl CHANGE id"
521                                           ." id INT NOT NULL AUTO_INCREMENT");
522                     $fields = mysql_list_fields($database, $page_tbl);
523                     if (!strstr(strtolower(mysql_field_flags($fields, $i)), "auto_increment"))
524                         echo " <b><font color=\"red\">", _("FAILED"), "</font></b><br />\n";
525                     else     
526                         echo _("OK"), "<br />\n";
527                 } else {
528                     echo _("OK"), "<br />\n";
529                 }
530                 break;
531             }
532         }
533         mysql_free_result($fields);
534     }
535
536     // Check for mysql 4.1.x/5.0.0a binary search problem.
537     //   http://bugs.mysql.com/bug.php?id=4398
538     // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
539     // Confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
540     // On windows only, though utf8 would be useful elsewhere also.
541     // Illegal mix of collations (latin1_bin,IMPLICIT) and 
542     // (utf8_general_ci, COERCIBLE) for operation '='])
543     if (isWindows() and substr($backend_type,0,5) == 'mysql') {
544         echo _("check for mysql 4.1.x/5.0.0 binary search on windows problem")," ...";
545         $mysql_version = $dbh->_backend->_serverinfo['version'];
546         if ($mysql_version < 401.0) { 
547             echo sprintf(_("version <em>%s</em>"), $mysql_version)," ",
548                 _("not affected"),"<br />\n";
549         } elseif ($mysql_version >= 401.6) { // FIXME: since which version?
550             $row = $dbh->_backend->getRow("SHOW CREATE TABLE $page_tbl");
551             $result = join(" ", $row);
552             if (strstr(strtolower($result), "character set") 
553                 and strstr(strtolower($result), "collate")) 
554             {
555                 echo _("OK"), "<br />\n";
556             } else {
557                 //SET CHARACTER SET latin1
558                 $charset = CHARSET;
559                 if ($charset == 'iso-8859-1') $charset = 'latin1';
560                 $dbh->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename "
561                                       ."pagename VARCHAR(100) "
562                                       ."CHARACTER SET '$charset' COLLATE '$charset"."_bin' NOT NULL");
563                 echo sprintf(_("version <em>%s</em>"), $mysql_version), 
564                     " <b>",_("FIXED"),"</b>",
565                     "<br />\n";
566             }
567         } elseif (DATABASE_TYPE != 'PDO') {
568             // check if already fixed
569             extract($dbh->_backend->_table_names);
570             assert(!empty($page_tbl));
571             $database = $dbh->_backend->database();
572             $fields = mysql_list_fields($database, $page_tbl, $dbh->_backend->connection());
573             $columns = mysql_num_fields($fields); 
574             for ($i = 0; $i < $columns; $i++) {
575                 if (mysql_field_name($fields, $i) == 'pagename') {
576                     $flags = mysql_field_flags($fields, $i);
577                     // I think it was fixed with 4.1.6, but I tested it only with 4.1.8
578                     if ($mysql_version > 401.0 and $mysql_version < 401.6) {
579                         // remove the binary flag
580                         if (strstr(strtolower($flags), "binary")) {
581                             // FIXME: on duplicate pagenames this will fail!
582                             $dbh->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename"
583                                                   ." pagename VARCHAR(100) NOT NULL");
584                             echo sprintf(_("version <em>%s</em>"), $mysql_version), 
585                                 "<b>",_("FIXED"),"</b>"
586                                 ,"<br />\n";    
587                         }
588                     }
589                     break;
590                 }
591             }
592         }
593     }
594     if ((ACCESS_LOG_SQL & 2)) {
595         echo _("check for ACCESS_LOG_SQL passwords in POST requests")," ...";
596         // Don't display passwords in POST requests (up to 2005-02-04 12:03:20)
597         $result = $dbh->genericSqlQuery(
598                     "UPDATE ".$prefix."accesslog"
599                     .' SET request_args=CONCAT(left(request_args, LOCATE("s:6:\"passwd\"",request_args)+12),"...")'
600                     .' WHERE LOCATE("s:6:\"passwd\"", request_args)'
601                     .' AND NOT(LOCATE("s:6:\"passwd\";s:15:\"<not displayed>\"", request_args))'
602                     .' AND request_method="POST"');
603         if ((DATABASE_TYPE == 'SQL' and $backend->AffectedRows()) 
604             or (DATABASE_TYPE == 'ADODB' and $backend->Affected_Rows())
605             or (DATABASE_TYPE == 'PDO' and $result))
606             echo "<b>",_("FIXED"),"</b>", "<br />\n";
607         else 
608             echo _("OK"),"<br />\n";
609     }
610     _upgrade_cached_html($dbh);
611
612     return;
613 }
614
615 function _upgrade_db_init (&$dbh) {
616     global $request, $DBParams, $DBAuthParams;
617     if (!$dbh->_backend->isSQL()) return;
618
619     if (DBADMIN_USER) {
620         // if need to connect as the root user, for CREATE and ALTER privileges
621         $AdminParams = $DBParams;
622         if (DATABASE_TYPE == 'SQL')
623             $dsn = DB::parseDSN($AdminParams['dsn']);
624         else // ADODB or PDO
625             $dsn = parseDSN($AdminParams['dsn']);
626         $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
627                                       $dsn['phptype'],
628                                       DBADMIN_USER,
629                                       DBADMIN_PASSWD,
630                                       $dsn['hostspec'],
631                                       $dsn['database']);
632         if (DEBUG & _DEBUG_SQL and DATABASE_TYPE == 'PDO') {
633             echo "<br>\nDBParams['dsn']: '", $DBParams['dsn'], "'";
634             echo "<br>\ndsn: '", print_r($dsn), "'";
635             echo "<br>\nAdminParams['dsn']: '", $AdminParams['dsn'], "'";
636         }
637         $dbh = WikiDB::open($AdminParams);
638     } elseif ($dbadmin = $request->getArg('dbadmin')) {
639         if (empty($dbadmin['user']) or isset($dbadmin['cancel']))
640             $dbh = &$request->_dbi;
641         else {
642             $AdminParams = $DBParams;
643             if (DATABASE_TYPE == 'SQL')
644                 $dsn = DB::parseDSN($AdminParams['dsn']);
645             else
646                 $dsn = parseDSN($AdminParams['dsn']);
647             $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
648                                       $dsn['phptype'],
649                                       $dbadmin['user'],
650                                       $dbadmin['passwd'],
651                                       $dsn['hostspec'],
652                                       $dsn['database']);
653             $dbh = WikiDB::open($AdminParams);
654         }
655     } else {
656         // Check if the privileges are enough. Need CREATE and ALTER perms. 
657         // And on windows: SELECT FROM mysql, possibly: UPDATE mysql.
658         $form = HTML::form(array("method" => "post", 
659                                  "action" => $request->getPostURL(),
660                                  "accept-charset"=>$GLOBALS['charset']),
661 HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."),
662                                    HTML::br(),
663 _("And on windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")),
664                            HiddenInputs(array('action' => 'upgrade',
665                                               'overwrite' => $request->getArg('overwrite'))),
666                            HTML::table(array("cellspacing"=>4),
667                                        HTML::tr(HTML::td(array('align'=>'right'),
668                                                          _("DB admin user:")),
669                                                 HTML::td(HTML::input(array('name'=>"dbadmin[user]",
670                                                                            'size'=>12,
671                                                                            'maxlength'=>256,
672                                                                            'value'=>'root')))),
673                                        HTML::tr(HTML::td(array('align'=>'right'),
674                                                          _("DB admin password:")),
675                                                 HTML::td(HTML::input(array('name'=>"dbadmin[passwd]",
676                                                                            'type'=>'password',
677                                                                            'size'=>12,
678                                                                            'maxlength'=>256)))),
679                                        HTML::tr(HTML::td(array('align'=>'center', 'colspan' => 2),
680                                                          Button("submit:", _("Submit"), 'wikiaction'), 
681                                                          HTML::raw('&nbsp;'),
682                                                          Button("submit:dbadmin[cancel]", _("Cancel"), 
683                                                                 'button')))));
684         $form->printXml();
685         echo "</div><!-- content -->\n";
686         echo asXML(Template("bottom"));
687         echo "</body></html>\n";
688         $request->finish();
689         exit();
690     }
691 }
692
693 /**
694  * if page.cached_html does not exists:
695  *   put _cached_html from pagedata into a new seperate blob, not huge serialized string.
696  *
697  * it is only rarelely needed: for current page only, if-not-modified
698  * but was extracetd for every simple page iteration.
699  */
700 function _upgrade_cached_html (&$dbh, $verbose=true) {
701     global $DBParams;
702     if (!$dbh->_backend->isSQL()) return;
703     //if (!in_array(DATABASE_TYPE, array('SQL','ADODB','PDO'))) return;
704     $count = 0;
705     if (phpwiki_version() >= 1030.10) {
706         if ($verbose)
707             echo _("check for extra page.cached_html column")," ... ";
708         $database = $dbh->_backend->database();
709         extract($dbh->_backend->_table_names);
710         $fields = $dbh->_backend->listOfFields($database, $page_tbl);
711         if (!$fields) {
712             echo _("SKIP"), "<br />\n";
713             return 0;
714         }
715         if (!strstr(strtolower(join(':', $fields)), "cached_html")) {
716             if ($verbose)
717                 echo "<b>",_("ADDING"),"</b>"," ... ";
718             $backend_type = $dbh->_backend->backendType();
719             if (substr($backend_type,0,5) == 'mysql')
720                 $dbh->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB");
721             else
722                 $dbh->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB");
723             if ($verbose)
724                 echo "<b>",_("CONVERTING"),"</b>"," ... ";
725             $count = _convert_cached_html($dbh);
726             if ($verbose)
727                 echo $count, " ", _("OK"), "<br />\n";
728         } else {
729             if ($verbose)
730                 echo _("OK"), "<br />\n";
731         }
732     }
733     return $count;
734 }
735
736 /** 
737  * move _cached_html for all pages from pagedata into a new seperate blob.
738  * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also.
739  */
740 function _convert_cached_html (&$dbh) {
741     global $DBParams;
742     if (!$dbh->_backend->isSQL()) return;
743     //if (!in_array(DATABASE_TYPE, array('SQL','ADODB'))) return;
744
745     $pages = $dbh->getAllPages();
746     $cache =& $dbh->_cache;
747     $count = 0;
748     extract($dbh->_backend->_table_names);
749     while ($page = $pages->next()) {
750         $pagename = $page->getName();
751         $data = $dbh->_backend->get_pagedata($pagename);
752         if (!empty($data['_cached_html'])) {
753             $cached_html = $data['_cached_html'];
754             $data['_cached_html'] = '';
755             $cache->update_pagedata($pagename, $data);
756             // store as blob, not serialized
757             $dbh->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?",
758                                   array($cached_html, $pagename));
759             $count++;
760         }
761     }
762     return $count;
763 }
764
765 function CheckPluginUpdate(&$request) {
766     echo "<h3>",_("check for necessary plugin argument updates"),"</h3>\n";
767     $process = array('msg' => _("change RandomPage pages => numpages"),
768                      'match' => "/(<\?\s*plugin\s+ RandomPage\s+)pages/",
769                      'replace' => "\\1numpages");
770     $dbi = $request->getDbh();
771     $allpages = $dbi->getAllPages(false);
772     while ($page = $allpages->next()) {
773         $current = $page->getCurrentRevision();
774         $pagetext = $current->getPackedContent();
775         foreach ($process as $p) {
776             if (preg_match($p['match'], $pagetext)) {
777                 echo $page->getName()," ",$p['msg']," ... ";
778                 if ($newtext = preg_replace($p['match'], $p['replace'], $pagetext)) {
779                     $meta = $current->_data;
780                     $meta['summary'] = "upgrade: ".$p['msg'];
781                     $page->save($newtext, $current->getVersion() + 1, $meta);
782                     echo _("OK"), "<br />\n";
783                 } else {
784                     echo " <b><font color=\"red\">", _("FAILED"), "</font></b><br />\n";
785                 }
786             }
787         }
788     }
789 }
790
791 function fixConfigIni($match, $new) {
792     $file = FindFile("config/config.ini");
793     $found = false;
794     if (is_writable($file)) {
795         $in = fopen($file,"rb");
796         $out = fopen($tmp = tempnam(FindFile("uploads"),"cfg"),"wb");
797         if (isWindows())
798             $tmp = str_replace("/","\\",$tmp);
799         while ($s = fgets($in)) {
800             if (preg_match($match, $s)) {
801                 $s = $new . (isWindows() ? "\r\n" : "\n");
802                 $found = true;
803             }
804             fputs($out, $s);
805         }
806         fclose($in);
807         fclose($out);
808         if (!$found) {
809             echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
810                 sprintf(_("%s not found"), $match);
811             unlink($out);
812         } else {
813             @unlink("$file.bak");
814             @rename($file,"$file.bak");
815             if (rename($tmp, $file))
816                 echo " <b>",_("FIXED"),"</b>";
817             else {
818                 echo " <b>",_("FAILED"),"</b>: ";
819                 sprintf(_("couldn't move %s to %s"), $tmp, $file);
820                 return false;
821             }
822         }
823         return $found;
824     } else {
825         echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
826             sprintf(_("%s is not writable"), $file);
827         return false;
828     }
829 }
830
831 function CheckConfigUpdate(&$request) {
832     echo "<h3>",_("check for necessary config updates"),"</h3>\n";
833     echo _("check for old CACHE_CONTROL = NONE")," ... ";
834     if (defined('CACHE_CONTROL') and CACHE_CONTROL == '') {
835         echo "<br />&nbsp;&nbsp;",
836             _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"),
837             " ...";
838         fixConfigIni("/^\s*CACHE_CONTROL\s*=\s*NONE/","CACHE_CONTROL = NO_CACHE");
839     } else {
840         echo _("OK");
841     }
842     echo "<br />\n";
843     echo _("check for GROUP_METHOD = NONE")," ... ";
844     if (defined('GROUP_METHOD') and GROUP_METHOD == '') {
845         echo "<br />&nbsp;&nbsp;",
846             _("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""),
847             " ...";
848         fixConfigIni("/^\s*GROUP_METHOD\s*=\s*NONE/","GROUP_METHOD = \"NONE\"");
849     } else {
850         echo _("OK");
851     }
852     echo "<br />\n";
853 }
854
855 /**
856  * TODO:
857  *
858  * Upgrade: Base class for multipage worksteps
859  * identify, validate, display options, next step
860  */
861 /*
862 class Upgrade {
863 }
864
865 class Upgrade_CheckPgsrc extends Upgrade {
866 }
867
868 class Upgrade_CheckDatabaseUpdate extends Upgrade {
869 }
870 */
871
872 // TODO: At which step are we? 
873 // validate and do it again or go on with next step.
874
875 /** entry function from lib/main.php
876  */
877 function DoUpgrade($request) {
878
879     if (!$request->_user->isAdmin()) {
880         $request->_notAuthorized(WIKIAUTH_ADMIN);
881         $request->finish(
882                          HTML::div(array('class' => 'disabled-plugin'),
883                                    fmt("Upgrade disabled: user != isAdmin")));
884         return;
885     }
886
887     StartLoadDump($request, _("Upgrading this PhpWiki"));
888     //CheckOldIndexUpdate($request); // to upgrade from < 1.3.10
889     CheckDatabaseUpdate($request);   // first check cached_html and friends
890     CheckActionPageUpdate($request);
891     CheckPgsrcUpdate($request);
892     //CheckThemeUpdate($request);
893     //CheckPluginUpdate($request);
894     CheckConfigUpdate($request);
895     EndLoadDump($request);
896 }
897
898
899 /*
900  $Log: not supported by cvs2svn $
901  Revision 1.49  2006/05/18 06:03:39  rurban
902  use $dbh->_backend->isSQL
903
904  Revision 1.48  2005/11/14 22:32:38  rurban
905  remove user, SKIP on !session
906
907  Revision 1.47  2005/02/27 19:13:27  rurban
908  latin1 mysql fix
909
910  Revision 1.46  2005/02/12 17:22:18  rurban
911  locale update: missing . : fixed. unified strings
912  proper linebreaks
913
914  Revision 1.45  2005/02/10 19:01:19  rurban
915  add PDO support
916
917  Revision 1.44  2005/02/07 15:40:42  rurban
918  use defined CHARSET for db. more comments
919
920  Revision 1.43  2005/02/04 11:44:07  rurban
921  check passwd in access_log
922
923  Revision 1.42  2005/02/02 19:38:13  rurban
924  prefer utf8 pagenames for collate issues
925
926  Revision 1.41  2005/01/31 12:15:29  rurban
927  print OK
928
929  Revision 1.40  2005/01/30 23:22:17  rurban
930  clarify messages
931
932  Revision 1.39  2005/01/30 23:09:17  rurban
933  sanify session fields
934
935  Revision 1.38  2005/01/25 07:57:02  rurban
936  add dbadmin form, add mysql LOCK TABLES check, add plugin args updater (not yet activated)
937
938  Revision 1.37  2005/01/20 10:19:08  rurban
939  add InterWikiMap to special pages
940
941  Revision 1.36  2004/12/20 12:56:11  rurban
942  patch #1088128 by Kai Krakow. avoid chicken & egg problem
943
944  Revision 1.35  2004/12/13 14:35:41  rurban
945  verbose arg
946
947  Revision 1.34  2004/12/11 09:39:28  rurban
948  needed init for ref
949
950  Revision 1.33  2004/12/10 22:33:39  rurban
951  add WikiAdminUtils method for convert-cached-html
952  missed some vars.
953
954  Revision 1.32  2004/12/10 22:15:00  rurban
955  fix $page->get('_cached_html)
956  refactor upgrade db helper _convert_cached_html() to be able to call them from WikiAdminUtils also.
957  support 2nd genericSqlQuery param (bind huge arg)
958
959  Revision 1.31  2004/12/10 02:45:26  rurban
960  SQL optimization:
961    put _cached_html from pagedata into a new seperate blob, not huge serialized string.
962    it is only rarelely needed: for current page only, if-not-modified
963    but was extracted for every simple page iteration.
964
965  Revision 1.30  2004/11/29 17:58:57  rurban
966  just aesthetics
967
968  Revision 1.29  2004/11/29 16:08:31  rurban
969  added missing nl
970
971  Revision 1.28  2004/11/16 16:25:14  rurban
972  fix accesslog tablename, print CREATED only if really done
973
974  Revision 1.27  2004/11/07 16:02:52  rurban
975  new sql access log (for spam prevention), and restructured access log class
976  dbh->quote (generic)
977  pear_db: mysql specific parts seperated (using replace)
978
979  Revision 1.26  2004/10/14 19:19:34  rurban
980  loadsave: check if the dumped file will be accessible from outside.
981  and some other minor fixes. (cvsclient native not yet ready)
982
983  Revision 1.25  2004/09/06 08:28:00  rurban
984  rename genericQuery to genericSqlQuery
985
986  Revision 1.24  2004/07/05 13:56:22  rurban
987  sqlite autoincrement fix
988
989  Revision 1.23  2004/07/04 10:28:06  rurban
990  DBADMIN_USER fix
991
992  Revision 1.22  2004/07/03 17:21:28  rurban
993  updated docs: submitted new mysql bugreport (#1491 did not fix it)
994
995  Revision 1.21  2004/07/03 16:51:05  rurban
996  optional DBADMIN_USER:DBADMIN_PASSWD for action=upgrade (if no ALTER permission)
997  added atomic mysql REPLACE for PearDB as in ADODB
998  fixed _lock_tables typo links => link
999  fixes unserialize ADODB bug in line 180
1000
1001  Revision 1.20  2004/07/03 14:48:18  rurban
1002  Tested new mysql 4.1.3-beta: binary search bug as fixed.
1003  => fixed action=upgrade,
1004  => version check in PearDB also (as in ADODB)
1005
1006  Revision 1.19  2004/06/19 12:19:09  rurban
1007  slightly improved docs
1008
1009  Revision 1.18  2004/06/19 11:47:17  rurban
1010  added CheckConfigUpdate: CACHE_CONTROL = NONE => NO_CACHE
1011
1012  Revision 1.17  2004/06/17 11:31:50  rurban
1013  check necessary localized actionpages
1014
1015  Revision 1.16  2004/06/16 10:38:58  rurban
1016  Disallow refernces in calls if the declaration is a reference
1017  ("allow_call_time_pass_reference clean").
1018    PhpWiki is now allow_call_time_pass_reference = Off clean,
1019    but several external libraries may not.
1020    In detail these libs look to be affected (not tested):
1021    * Pear_DB odbc
1022    * adodb oracle
1023
1024  Revision 1.15  2004/06/07 19:50:40  rurban
1025  add owner field to mimified dump
1026
1027  Revision 1.14  2004/06/07 18:38:18  rurban
1028  added mysql 4.1.x search fix
1029
1030  Revision 1.13  2004/06/04 20:32:53  rurban
1031  Several locale related improvements suggested by Pierrick Meignen
1032  LDAP fix by John Cole
1033  reanable admin check without ENABLE_PAGEPERM in the admin plugins
1034
1035  Revision 1.12  2004/05/18 13:59:15  rurban
1036  rename simpleQuery to genericSqlQuery
1037
1038  Revision 1.11  2004/05/15 13:06:17  rurban
1039  skip the HomePage, at first upgrade the ActionPages, then the database, then the rest
1040
1041  Revision 1.10  2004/05/15 01:19:41  rurban
1042  upgrade prefix fix by Kai Krakow
1043
1044  Revision 1.9  2004/05/14 11:33:03  rurban
1045  version updated to 1.3.11pre
1046  upgrade stability fix
1047
1048  Revision 1.8  2004/05/12 10:49:55  rurban
1049  require_once fix for those libs which are loaded before FileFinder and
1050    its automatic include_path fix, and where require_once doesn't grok
1051    dirname(__FILE__) != './lib'
1052  upgrade fix with PearDB
1053  navbar.tmpl: remove spaces for IE &nbsp; button alignment
1054
1055  Revision 1.7  2004/05/06 17:30:38  rurban
1056  CategoryGroup: oops, dos2unix eol
1057  improved phpwiki_version:
1058    pre -= .0001 (1.3.10pre: 1030.099)
1059    -p1 += .001 (1.3.9-p1: 1030.091)
1060  improved InstallTable for mysql and generic SQL versions and all newer tables so far.
1061  abstracted more ADODB/PearDB methods for action=upgrade stuff:
1062    backend->backendType(), backend->database(),
1063    backend->listOfFields(),
1064    backend->listOfTables(),
1065
1066  Revision 1.6  2004/05/03 15:05:36  rurban
1067  + table messages
1068
1069  Revision 1.4  2004/05/02 21:26:38  rurban
1070  limit user session data (HomePageHandle and auth_dbi have to invalidated anyway)
1071    because they will not survive db sessions, if too large.
1072  extended action=upgrade
1073  some WikiTranslation button work
1074  revert WIKIAUTH_UNOBTAINABLE (need it for main.php)
1075  some temp. session debug statements
1076
1077  Revision 1.3  2004/04/29 22:33:30  rurban
1078  fixed sf.net bug #943366 (Kai Krakow)
1079    couldn't load localized url-undecoded pagenames
1080
1081  Revision 1.2  2004/03/12 15:48:07  rurban
1082  fixed explodePageList: wrong sortby argument order in UnfoldSubpages
1083  simplified lib/stdlib.php:explodePageList
1084
1085  */
1086
1087 // For emacs users
1088 // Local Variables:
1089 // mode: php
1090 // tab-width: 8
1091 // c-basic-offset: 4
1092 // c-hanging-comment-ender-p: nil
1093 // indent-tabs-mode: nil
1094 // End:
1095 ?>