]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/upgrade.php
locale update: missing . : fixed. unified strings
[SourceForge/phpwiki.git] / lib / upgrade.php
1 <?php //-*-php-*-
2 rcs_id('$Id: upgrade.php,v 1.46 2005-02-12 17:22:18 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','PDO'))) 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 $DBParams, $DBAuthParams;
355     if (!in_array($DBParams['dbtype'], array('SQL','ADODB','PDO'))) return;
356     echo "<h3>",_("check for necessary database updates"), " - ", $DBParams['dbtype'], "</h3>\n";
357
358     $dbh = $request->getDbh(); 
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:user: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 (!strstr(strtolower(join(':', $sess_fields)), "sess_ip")) {
407             // TODO: postgres test (should be able to add columns at the end, but not in between)
408             echo "<b>",_("ADDING"),"</b>"," ... ";              
409             $dbh->genericSqlQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
410             $dbh->genericSqlQuery("CREATE INDEX sess_date ON $session_tbl (sess_date)");
411         } else {
412             echo _("OK");
413         }
414         echo "<br />\n";
415         if (substr($backend_type,0,5) == 'mysql') {
416             // upgrade to 4.1.8 destroyed my session table: 
417             // sess_id => varchar(10), sess_data => varchar(5). For others obviously also.
418             echo _("check for mysql session.sess_id sanity")," ... ";
419             $result = $dbh->genericSqlQuery("DESCRIBE $session_tbl");
420             if ($DBParams['dbtype'] == 'SQL') {
421                 $iter = new WikiDB_backend_PearDB_generic_iter($backend, $result);
422             } elseif ($DBParams['dbtype'] == 'ADODB') {
423                 $iter = new WikiDB_backend_ADODB_generic_iter($backend, $result, 
424                                 array("Field", "Type", "Null", "Key", "Default", "Extra"));
425             } elseif ($DBParams['dbtype'] == 'PDO') {
426                 $iter = new WikiDB_backend_PDO_generic_iter($backend, $result);
427             }
428             while ($col = $iter->next()) {
429                 if ($col["Field"] == 'sess_id' and !strstr(strtolower($col["Type"]), 'char(32)')) {
430                     $dbh->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_id"
431                                           ." sess_id CHAR(32) NOT NULL");
432                     echo "sess_id ", $col["Type"], " ", _("fixed"), " =&gt; CHAR(32) ";
433                 }
434                 if ($col["Field"] == 'sess_ip' and !strstr(strtolower($col["Type"]), 'char(15)')) {
435                     $dbh->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_ip"
436                                           ." sess_ip CHAR(15) NOT NULL");
437                     echo "sess_ip ", $col["Type"], " ", _("fixed"), " =&gt; CHAR(15) ";
438                 }
439             }
440             echo _("OK"), "<br />\n";
441         }
442     }
443
444     // mysql >= 4.0.4 requires LOCK TABLE privileges
445     if (substr($backend_type,0,5) == 'mysql'/* and $DBParams['dbtype'] != 'PDO' */) {
446         echo _("check for mysql LOCK TABLE privilege")," ...";
447         $mysql_version = $dbh->_backend->_serverinfo['version'];
448         if ($mysql_version > 400.40) {
449             if (!empty($dbh->_backend->_parsedDSN))
450                 $parseDSN = $dbh->_backend->_parsedDSN;
451             elseif (function_exists('parseDSN')) // ADODB or PDO
452                 $parseDSN = parseDSN($DBParams['dsn']);
453             else                             // pear
454                 $parseDSN = DB::parseDSN($DBParams['dsn']);
455             $username = $dbh->_backend->qstr($parseDSN['username']);
456             // on db level
457             $query = "SELECT lock_tables_priv FROM mysql.db WHERE user='$username'";
458             //mysql_select_db("mysql", $dbh->_backend->connection());
459             $db_fields = $dbh->_backend->listOfFields("mysql", "db");
460             if (!strstr(strtolower(join(':', $db_fields)), "lock_tables_priv")) {
461                 echo join(':', $db_fields);
462                 die("lock_tables_priv missing. The DB Admin must run mysql_fix_privilege_tables");
463             }
464             $row = $dbh->_backend->getRow($query);
465             if (isset($row[0]) and $row[0] == 'N') {
466                 $dbh->genericSqlQuery("UPDATE mysql.db SET lock_tables_priv='Y'"
467                                       ." WHERE mysql.user='$username'");
468                 $dbh->genericSqlQuery("FLUSH PRIVILEGES");
469                 echo "mysql.db user='$username'", _("fixed"), "<br />\n";
470             } elseif (!$row) {
471                 // or on user level
472                 $query = "SELECT lock_tables_priv FROM mysql.user WHERE user='$username'";
473                 $row = $dbh->_backend->getRow($query);
474                 if ($row and $row[0] == 'N') {
475                     $dbh->genericSqlQuery("UPDATE mysql.user SET lock_tables_priv='Y'"
476                                           ." WHERE mysql.user='$username'");
477                     $dbh->genericSqlQuery("FLUSH PRIVILEGES");
478                     echo "mysql.user user='$username'", _("fixed"), "<br />\n";
479                 } elseif (!$row) {
480                     echo " <b><font color=\"red\">", _("FAILED"), "</font></b>: ",
481                         "Neither mysql.db nor mysql.user has a user='$username'"
482                         ." or the lock_tables_priv field",
483                         "<br />\n";
484                 } else {
485                     echo _("OK"), "<br />\n";
486                 }
487             } else {
488                 echo _("OK"), "<br />\n";
489             }
490             //mysql_select_db($dbh->_backend->database(), $dbh->_backend->connection());
491         } else {
492             echo sprintf(_("version <em>%s</em> not affected"), $mysql_version),"<br />\n";
493         }
494     }
495
496     // 1.3.10 mysql requires page.id auto_increment
497     // mysql, mysqli or mysqlt
498     if (phpwiki_version() >= 1030.099 and substr($backend_type,0,5) == 'mysql' 
499         and $DBParams['dbtype'] != 'PDO') {
500         echo _("check for mysql page.id auto_increment flag")," ...";
501         assert(!empty($page_tbl));
502         $database = $dbh->_backend->database();
503         $fields = mysql_list_fields($database, $page_tbl, $dbh->_backend->connection());
504         $columns = mysql_num_fields($fields); 
505         for ($i = 0; $i < $columns; $i++) {
506             if (mysql_field_name($fields, $i) == 'id') {
507                 $flags = mysql_field_flags($fields, $i);
508                 //DONE: something was wrong with ADODB here.
509                 if (!strstr(strtolower($flags), "auto_increment")) {
510                     echo "<b>",_("ADDING"),"</b>"," ... ";
511                     // MODIFY col_def valid since mysql 3.22.16,
512                     // older mysql's need CHANGE old_col col_def
513                     $dbh->genericSqlQuery("ALTER TABLE $page_tbl CHANGE id"
514                                           ." id INT NOT NULL AUTO_INCREMENT");
515                     $fields = mysql_list_fields($database, $page_tbl);
516                     if (!strstr(strtolower(mysql_field_flags($fields, $i)), "auto_increment"))
517                         echo " <b><font color=\"red\">", _("FAILED"), "</font></b><br />\n";
518                     else     
519                         echo _("OK"), "<br />\n";
520                 } else {
521                     echo _("OK"), "<br />\n";
522                 }
523                 break;
524             }
525         }
526         mysql_free_result($fields);
527     }
528
529     // Check for mysql 4.1.x/5.0.0a binary search problem.
530     //   http://bugs.mysql.com/bug.php?id=4398
531     // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
532     // Confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
533     // On windows only, though utf8 would be useful elsewhere also.
534     // Illegal mix of collations (latin1_bin,IMPLICIT) and 
535     // (utf8_general_ci, COERCIBLE) for operation '='])
536     if (isWindows() and substr($backend_type,0,5) == 'mysql') {
537         echo _("check for mysql 4.1.x/5.0.0 binary search on windows problem")," ...";
538         $mysql_version = $dbh->_backend->_serverinfo['version'];
539         if ($mysql_version < 401.0) { 
540             echo sprintf(_("version <em>%s</em>"), $mysql_version)," ",
541                 _("not affected"),"<br />\n";
542         } elseif ($mysql_version >= 401.6) { // FIXME: since which version?
543             $row = $dbh->_backend->getRow("SHOW CREATE TABLE $page_tbl");
544             $result = join(" ", $row);
545             if (strstr(strtolower($result), "character set") 
546                 and strstr(strtolower($result), "collate")) 
547             {
548                 echo _("OK"), "<br />\n";
549             } else {
550                 //SET CHARACTER SET latin1
551                 $dbh->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename "
552                                       ."pagename VARCHAR(100) CHARACTER SET ".CHARSET
553                                       ." COLLATE ".CHARSET."_bin NOT NULL");
554                 echo sprintf(_("version <em>%s</em>"), $mysql_version), 
555                     "<b>",_("FIXED"),"</b>",
556                     "<br />\n";
557             }
558         } elseif ($DBParams['dbtype'] != 'PDO') {
559             // check if already fixed
560             extract($dbh->_backend->_table_names);
561             assert(!empty($page_tbl));
562             $database = $dbh->_backend->database();
563             $fields = mysql_list_fields($database, $page_tbl, $dbh->_backend->connection());
564             $columns = mysql_num_fields($fields); 
565             for ($i = 0; $i < $columns; $i++) {
566                 if (mysql_field_name($fields, $i) == 'pagename') {
567                     $flags = mysql_field_flags($fields, $i);
568                     // I think it was fixed with 4.1.6, but I tested it only with 4.1.8
569                     if ($mysql_version > 401.0 and $mysql_version < 401.6) {
570                         // remove the binary flag
571                         if (strstr(strtolower($flags), "binary")) {
572                             // FIXME: on duplicate pagenames this will fail!
573                             $dbh->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename"
574                                                   ." pagename VARCHAR(100) NOT NULL");
575                             echo sprintf(_("version <em>%s</em>"), $mysql_version), 
576                                 "<b>",_("FIXED"),"</b>"
577                                 ,"<br />\n";    
578                         }
579                     }
580                     break;
581                 }
582             }
583         }
584     }
585     if ((ACCESS_LOG_SQL & 2)) {
586         echo _("check for ACCESS_LOG_SQL passwords in POST requests")," ...";
587         // Don't display passwords in POST requests (up to 2005-02-04 12:03:20)
588         $result = $dbh->genericSqlQuery(
589                     "UPDATE ".$prefix."accesslog"
590                     .' SET request_args=CONCAT(left(request_args, LOCATE("s:6:\"passwd\"",request_args)+12),"...")'
591                     .' WHERE LOCATE("s:6:\"passwd\"", request_args)'
592                     .' AND NOT(LOCATE("s:6:\"passwd\";s:15:\"<not displayed>\"", request_args))'
593                     .' AND request_method="POST"');
594         if ((DATABASE_TYPE == 'SQL' and $backend->AffectedRows()) 
595             or (DATABASE_TYPE == 'ADODB' and $backend->Affected_Rows())
596             or (DATABASE_TYPE == 'PDO' and $result))
597             echo "<b>",_("FIXED"),"</b>", "<br />\n";
598         else 
599             echo _("OK"),"<br />\n";
600     }
601     _upgrade_cached_html($dbh);
602
603     return;
604 }
605
606 function _upgrade_db_init (&$dbh) {
607     global $request, $DBParams, $DBAuthParams;
608     if (!in_array($DBParams['dbtype'], array('SQL','ADODB','PDO'))) return;
609
610     if (DBADMIN_USER) {
611         // if need to connect as the root user, for CREATE and ALTER privileges
612         $AdminParams = $DBParams;
613         if ($DBParams['dbtype'] == 'SQL')
614             $dsn = DB::parseDSN($AdminParams['dsn']);
615         else // ADODB or PDO
616             $dsn = parseDSN($AdminParams['dsn']);
617         $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
618                                       $dsn['phptype'],
619                                       DBADMIN_USER,
620                                       DBADMIN_PASSWD,
621                                       $dsn['hostspec'],
622                                       $dsn['database']);
623         if (DEBUG & _DEBUG_SQL and $DBParams['dbtype'] == 'PDO') {
624             echo "<br>\nDBParams['dsn']: '", $DBParams['dsn'], "'";
625             echo "<br>\ndsn: '", print_r($dsn), "'";
626             echo "<br>\nAdminParams['dsn']: '", $AdminParams['dsn'], "'";
627         }
628         $dbh = WikiDB::open($AdminParams);
629     } elseif ($dbadmin = $request->getArg('dbadmin')) {
630         if (empty($dbadmin['user']) or isset($dbadmin['cancel']))
631             $dbh = &$request->_dbi;
632         else {
633             $AdminParams = $DBParams;
634             if ($DBParams['dbtype'] == 'SQL')
635                 $dsn = DB::parseDSN($AdminParams['dsn']);
636             else
637                 $dsn = parseDSN($AdminParams['dsn']);
638             $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
639                                       $dsn['phptype'],
640                                       $dbadmin['user'],
641                                       $dbadmin['passwd'],
642                                       $dsn['hostspec'],
643                                       $dsn['database']);
644             $dbh = WikiDB::open($AdminParams);
645         }
646     } else {
647         // Check if the privileges are enough. Need CREATE and ALTER perms. 
648         // And on windows: SELECT FROM mysql, possibly: UPDATE mysql.
649         $form = HTML::form(array("method" => "post", 
650                                  "action" => $request->getPostURL(),
651                                  "accept-charset"=>$GLOBALS['charset']),
652 HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."),
653                                    HTML::br(),
654 _("And on windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")),
655                            HiddenInputs(array('action' => 'upgrade')),
656                            HTML::table(array("cellspacing"=>4),
657                                        HTML::tr(HTML::td(array('align'=>'right'),
658                                                          _("DB admin user:")),
659                                                 HTML::td(HTML::input(array('name'=>"dbadmin[user]",
660                                                                            'size'=>12,
661                                                                            'maxlength'=>256,
662                                                                            'value'=>'root')))),
663                                        HTML::tr(HTML::td(array('align'=>'right'),
664                                                          _("DB admin password:")),
665                                                 HTML::td(HTML::input(array('name'=>"dbadmin[passwd]",
666                                                                            'type'=>'password',
667                                                                            'size'=>12,
668                                                                            'maxlength'=>256)))),
669                                        HTML::tr(HTML::td(array('align'=>'center', 'colspan' => 2),
670                                                          Button("submit:", _("Submit"), 'wikiaction'), 
671                                                          HTML::raw('&nbsp;'),
672                                                          Button("submit:dbadmin[cancel]", _("Cancel"), 
673                                                                 'button')))));
674         $form->printXml();
675         echo "</div><!-- content -->\n";
676         echo asXML(Template("bottom"));
677         echo "</body></html>\n";
678         $request->finish();
679         exit();
680     }
681 }
682
683 /**
684  * if page.cached_html does not exists:
685  *   put _cached_html from pagedata into a new seperate blob, not huge serialized string.
686  *
687  * it is only rarelely needed: for current page only, if-not-modified
688  * but was extracetd for every simple page iteration.
689  */
690 function _upgrade_cached_html (&$dbh, $verbose=true) {
691     global $DBParams;
692     if (!in_array($DBParams['dbtype'], array('SQL','ADODB'))) return;
693     $count = 0;
694     if (phpwiki_version() >= 1030.10) {
695         if ($verbose)
696             echo _("check for extra page.cached_html column")," ... ";
697         $database = $dbh->_backend->database();
698         extract($dbh->_backend->_table_names);
699         $fields = $dbh->_backend->listOfFields($database, $page_tbl);
700         if (!strstr(strtolower(join(':', $fields)), "cached_html")) {
701             if ($verbose)
702                 echo "<b>",_("ADDING"),"</b>"," ... ";
703             $backend_type = $dbh->_backend->backendType();
704             if (substr($backend_type,0,5) == 'mysql')
705                 $dbh->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB");
706             else
707                 $dbh->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB");
708             if ($verbose)
709                 echo "<b>",_("CONVERTING"),"</b>"," ... ";
710             $count = _convert_cached_html($dbh);
711             if ($verbose)
712                 echo $count, " ", _("OK"), "<br />\n";
713         } else {
714             if ($verbose)
715                 echo _("OK"), "<br />\n";
716         }
717     }
718     return $count;
719 }
720
721 /** 
722  * move _cached_html for all pages from pagedata into a new seperate blob.
723  * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also.
724  */
725 function _convert_cached_html (&$dbh) {
726     global $DBParams;
727     if (!in_array($DBParams['dbtype'], array('SQL','ADODB'))) return;
728
729     $pages = $dbh->getAllPages();
730     $cache =& $dbh->_cache;
731     $count = 0;
732     extract($dbh->_backend->_table_names);
733     while ($page = $pages->next()) {
734         $pagename = $page->getName();
735         $data = $dbh->_backend->get_pagedata($pagename);
736         if (!empty($data['_cached_html'])) {
737             $cached_html = $data['_cached_html'];
738             $data['_cached_html'] = '';
739             $cache->update_pagedata($pagename, $data);
740             // store as blob, not serialized
741             $dbh->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?",
742                                   array($cached_html, $pagename));
743             $count++;
744         }
745     }
746     return $count;
747 }
748
749 function CheckPluginUpdate(&$request) {
750     echo "<h3>",_("check for necessary plugin argument updates"),"</h3>\n";
751     $process = array('msg' => _("change RandomPage pages => numpages"),
752                      'match' => "/(<\?\s*plugin\s+ RandomPage\s+)pages/",
753                      'replace' => "\\1numpages");
754     $dbi = $request->getDbh();
755     $allpages = $dbi->getAllPages(false);
756     while ($page = $allpages->next()) {
757         $current = $page->getCurrentRevision();
758         $pagetext = $current->getPackedContent();
759         foreach ($process as $p) {
760             if (preg_match($p['match'], $pagetext)) {
761                 echo $page->getName()," ",$p['msg']," ... ";
762                 if ($newtext = preg_replace($p['match'], $p['replace'], $pagetext)) {
763                     $meta = $current->_data;
764                     $meta['summary'] = "upgrade: ".$p['msg'];
765                     $page->save($newtext, $current->getVersion() + 1, $meta);
766                     echo _("OK"), "<br />\n";
767                 } else {
768                     echo " <b><font color=\"red\">", _("FAILED"), "</font></b><br />\n";
769                 }
770             }
771         }
772     }
773 }
774
775 function fixConfigIni($match, $new) {
776     $file = FindFile("config/config.ini");
777     $found = false;
778     if (is_writable($file)) {
779         $in = fopen($file,"rb");
780         $out = fopen($tmp = tempnam(FindFile("uploads"),"cfg"),"wb");
781         if (isWindows())
782             $tmp = str_replace("/","\\",$tmp);
783         while ($s = fgets($in)) {
784             if (preg_match($match, $s)) {
785                 $s = $new . (isWindows() ? "\r\n" : "\n");
786                 $found = true;
787             }
788             fputs($out, $s);
789         }
790         fclose($in);
791         fclose($out);
792         if (!$found) {
793             echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
794                 sprintf(_("%s not found"), $match);
795             unlink($out);
796         } else {
797             @unlink("$file.bak");
798             @rename($file,"$file.bak");
799             if (rename($tmp, $file))
800                 echo " <b>",_("FIXED"),"</b>";
801             else {
802                 echo " <b>",_("FAILED"),"</b>: ";
803                 sprintf(_("couldn't move %s to %s"), $tmp, $file);
804                 return false;
805             }
806         }
807         return $found;
808     } else {
809         echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
810             sprintf(_("%s is not writable"), $file);
811         return false;
812     }
813 }
814
815 function CheckConfigUpdate(&$request) {
816     echo "<h3>",_("check for necessary config updates"),"</h3>\n";
817     echo _("check for old CACHE_CONTROL = NONE")," ... ";
818     if (defined('CACHE_CONTROL') and CACHE_CONTROL == '') {
819         echo "<br />&nbsp;&nbsp;",
820             _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"),
821             " ...";
822         fixConfigIni("/^\s*CACHE_CONTROL\s*=\s*NONE/","CACHE_CONTROL = NO_CACHE");
823     } else {
824         echo _("OK");
825     }
826     echo "<br />\n";
827     echo _("check for GROUP_METHOD = NONE")," ... ";
828     if (defined('GROUP_METHOD') and GROUP_METHOD == '') {
829         echo "<br />&nbsp;&nbsp;",
830             _("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""),
831             " ...";
832         fixConfigIni("/^\s*GROUP_METHOD\s*=\s*NONE/","GROUP_METHOD = \"NONE\"");
833     } else {
834         echo _("OK");
835     }
836     echo "<br />\n";
837 }
838
839 /**
840  * TODO:
841  *
842  * Upgrade: Base class for multipage worksteps
843  * identify, validate, display options, next step
844  */
845 /*
846 class Upgrade {
847 }
848
849 class Upgrade_CheckPgsrc extends Upgrade {
850 }
851
852 class Upgrade_CheckDatabaseUpdate extends Upgrade {
853 }
854 */
855
856 // TODO: At which step are we? 
857 // validate and do it again or go on with next step.
858
859 /** entry function from lib/main.php
860  */
861 function DoUpgrade($request) {
862
863     if (!$request->_user->isAdmin()) {
864         $request->_notAuthorized(WIKIAUTH_ADMIN);
865         $request->finish(
866                          HTML::div(array('class' => 'disabled-plugin'),
867                                    fmt("Upgrade disabled: user != isAdmin")));
868         return;
869     }
870
871     StartLoadDump($request, _("Upgrading this PhpWiki"));
872     //CheckOldIndexUpdate($request); // to upgrade from < 1.3.10
873     CheckDatabaseUpdate($request);   // first check cached_html and friends
874     CheckActionPageUpdate($request);
875     CheckPgsrcUpdate($request);
876     //CheckThemeUpdate($request);
877     //CheckPluginUpdate($request);
878     CheckConfigUpdate($request);
879     EndLoadDump($request);
880 }
881
882
883 /*
884  $Log: not supported by cvs2svn $
885  Revision 1.45  2005/02/10 19:01:19  rurban
886  add PDO support
887
888  Revision 1.44  2005/02/07 15:40:42  rurban
889  use defined CHARSET for db. more comments
890
891  Revision 1.43  2005/02/04 11:44:07  rurban
892  check passwd in access_log
893
894  Revision 1.42  2005/02/02 19:38:13  rurban
895  prefer utf8 pagenames for collate issues
896
897  Revision 1.41  2005/01/31 12:15:29  rurban
898  print OK
899
900  Revision 1.40  2005/01/30 23:22:17  rurban
901  clarify messages
902
903  Revision 1.39  2005/01/30 23:09:17  rurban
904  sanify session fields
905
906  Revision 1.38  2005/01/25 07:57:02  rurban
907  add dbadmin form, add mysql LOCK TABLES check, add plugin args updater (not yet activated)
908
909  Revision 1.37  2005/01/20 10:19:08  rurban
910  add InterWikiMap to special pages
911
912  Revision 1.36  2004/12/20 12:56:11  rurban
913  patch #1088128 by Kai Krakow. avoid chicken & egg problem
914
915  Revision 1.35  2004/12/13 14:35:41  rurban
916  verbose arg
917
918  Revision 1.34  2004/12/11 09:39:28  rurban
919  needed init for ref
920
921  Revision 1.33  2004/12/10 22:33:39  rurban
922  add WikiAdminUtils method for convert-cached-html
923  missed some vars.
924
925  Revision 1.32  2004/12/10 22:15:00  rurban
926  fix $page->get('_cached_html)
927  refactor upgrade db helper _convert_cached_html() to be able to call them from WikiAdminUtils also.
928  support 2nd genericSqlQuery param (bind huge arg)
929
930  Revision 1.31  2004/12/10 02:45:26  rurban
931  SQL optimization:
932    put _cached_html from pagedata into a new seperate blob, not huge serialized string.
933    it is only rarelely needed: for current page only, if-not-modified
934    but was extracted for every simple page iteration.
935
936  Revision 1.30  2004/11/29 17:58:57  rurban
937  just aesthetics
938
939  Revision 1.29  2004/11/29 16:08:31  rurban
940  added missing nl
941
942  Revision 1.28  2004/11/16 16:25:14  rurban
943  fix accesslog tablename, print CREATED only if really done
944
945  Revision 1.27  2004/11/07 16:02:52  rurban
946  new sql access log (for spam prevention), and restructured access log class
947  dbh->quote (generic)
948  pear_db: mysql specific parts seperated (using replace)
949
950  Revision 1.26  2004/10/14 19:19:34  rurban
951  loadsave: check if the dumped file will be accessible from outside.
952  and some other minor fixes. (cvsclient native not yet ready)
953
954  Revision 1.25  2004/09/06 08:28:00  rurban
955  rename genericQuery to genericSqlQuery
956
957  Revision 1.24  2004/07/05 13:56:22  rurban
958  sqlite autoincrement fix
959
960  Revision 1.23  2004/07/04 10:28:06  rurban
961  DBADMIN_USER fix
962
963  Revision 1.22  2004/07/03 17:21:28  rurban
964  updated docs: submitted new mysql bugreport (#1491 did not fix it)
965
966  Revision 1.21  2004/07/03 16:51:05  rurban
967  optional DBADMIN_USER:DBADMIN_PASSWD for action=upgrade (if no ALTER permission)
968  added atomic mysql REPLACE for PearDB as in ADODB
969  fixed _lock_tables typo links => link
970  fixes unserialize ADODB bug in line 180
971
972  Revision 1.20  2004/07/03 14:48:18  rurban
973  Tested new mysql 4.1.3-beta: binary search bug as fixed.
974  => fixed action=upgrade,
975  => version check in PearDB also (as in ADODB)
976
977  Revision 1.19  2004/06/19 12:19:09  rurban
978  slightly improved docs
979
980  Revision 1.18  2004/06/19 11:47:17  rurban
981  added CheckConfigUpdate: CACHE_CONTROL = NONE => NO_CACHE
982
983  Revision 1.17  2004/06/17 11:31:50  rurban
984  check necessary localized actionpages
985
986  Revision 1.16  2004/06/16 10:38:58  rurban
987  Disallow refernces in calls if the declaration is a reference
988  ("allow_call_time_pass_reference clean").
989    PhpWiki is now allow_call_time_pass_reference = Off clean,
990    but several external libraries may not.
991    In detail these libs look to be affected (not tested):
992    * Pear_DB odbc
993    * adodb oracle
994
995  Revision 1.15  2004/06/07 19:50:40  rurban
996  add owner field to mimified dump
997
998  Revision 1.14  2004/06/07 18:38:18  rurban
999  added mysql 4.1.x search fix
1000
1001  Revision 1.13  2004/06/04 20:32:53  rurban
1002  Several locale related improvements suggested by Pierrick Meignen
1003  LDAP fix by John Cole
1004  reanable admin check without ENABLE_PAGEPERM in the admin plugins
1005
1006  Revision 1.12  2004/05/18 13:59:15  rurban
1007  rename simpleQuery to genericSqlQuery
1008
1009  Revision 1.11  2004/05/15 13:06:17  rurban
1010  skip the HomePage, at first upgrade the ActionPages, then the database, then the rest
1011
1012  Revision 1.10  2004/05/15 01:19:41  rurban
1013  upgrade prefix fix by Kai Krakow
1014
1015  Revision 1.9  2004/05/14 11:33:03  rurban
1016  version updated to 1.3.11pre
1017  upgrade stability fix
1018
1019  Revision 1.8  2004/05/12 10:49:55  rurban
1020  require_once fix for those libs which are loaded before FileFinder and
1021    its automatic include_path fix, and where require_once doesn't grok
1022    dirname(__FILE__) != './lib'
1023  upgrade fix with PearDB
1024  navbar.tmpl: remove spaces for IE &nbsp; button alignment
1025
1026  Revision 1.7  2004/05/06 17:30:38  rurban
1027  CategoryGroup: oops, dos2unix eol
1028  improved phpwiki_version:
1029    pre -= .0001 (1.3.10pre: 1030.099)
1030    -p1 += .001 (1.3.9-p1: 1030.091)
1031  improved InstallTable for mysql and generic SQL versions and all newer tables so far.
1032  abstracted more ADODB/PearDB methods for action=upgrade stuff:
1033    backend->backendType(), backend->database(),
1034    backend->listOfFields(),
1035    backend->listOfTables(),
1036
1037  Revision 1.6  2004/05/03 15:05:36  rurban
1038  + table messages
1039
1040  Revision 1.4  2004/05/02 21:26:38  rurban
1041  limit user session data (HomePageHandle and auth_dbi have to invalidated anyway)
1042    because they will not survive db sessions, if too large.
1043  extended action=upgrade
1044  some WikiTranslation button work
1045  revert WIKIAUTH_UNOBTAINABLE (need it for main.php)
1046  some temp. session debug statements
1047
1048  Revision 1.3  2004/04/29 22:33:30  rurban
1049  fixed sf.net bug #943366 (Kai Krakow)
1050    couldn't load localized url-undecoded pagenames
1051
1052  Revision 1.2  2004/03/12 15:48:07  rurban
1053  fixed explodePageList: wrong sortby argument order in UnfoldSubpages
1054  simplified lib/stdlib.php:explodePageList
1055
1056  */
1057
1058 // For emacs users
1059 // Local Variables:
1060 // mode: php
1061 // tab-width: 8
1062 // c-basic-offset: 4
1063 // c-hanging-comment-ender-p: nil
1064 // indent-tabs-mode: nil
1065 // End:
1066 ?>