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