]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/upgrade.php
#1535843 by matt brown: Upgrade Wizard Password fixes are not portable
[SourceForge/phpwiki.git] / lib / upgrade.php
1 <?php //-*-php-*-
2 rcs_id('$Id: upgrade.php,v 1.54 2006-12-03 17:07:29 rurban Exp $');
3 /*
4  Copyright 2004,2005,2006 $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(100),
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         $res = $dbh->genericSqlIter("SELECT time_stamp, remote_host, " .
579             "request_args FROM ${prefix}accesslog WHERE request_args LIKE " .
580             "'%s:6:\"passwd\"%' AND request_args NOT LIKE '%s:6:\"passwd\";" .
581             "s:15:\"<not displayed>\"%'");
582         $count=0;
583         while ($row = $res->next()) {
584             $args = preg_replace("/(s:6:\"passwd\";s:15:\").*(\")/", 
585                 "$1<not displayed>$2", $row["request_args"]);
586             $ts = $row["time_stamp"];
587             $rh = $row["remote_host"];
588             $dbh->genericSqlQuery("UPDATE ${prefix}accesslog SET " .
589                 "request_args='$args' WHERE time_stamp=$ts AND " .
590                 "remote_host='$rh'");
591             $count++;
592         }
593         if ($count > 0)
594             echo "<b>",_("FIXED"),"</b>", "<br />\n";
595         else 
596             echo _("OK"),"<br />\n";
597     }
598     // TODO: change access_log.remote_host from varchar(50) to varchar(100)
599     _upgrade_cached_html($dbh);
600
601     return;
602 }
603
604 function _upgrade_db_init (&$dbh) {
605     global $request, $DBParams, $DBAuthParams;
606     if (!$dbh->_backend->isSQL()) return;
607
608     /* SQLite never needs admin params */
609     $backend_type = $dbh->_backend->backendType();
610     if (substr($backend_type,0,6)=="sqlite") {
611         return;
612     }
613
614     if (DBADMIN_USER) {
615         // if need to connect as the root user, for CREATE and ALTER privileges
616         $AdminParams = $DBParams;
617         if (DATABASE_TYPE == 'SQL')
618             $dsn = DB::parseDSN($AdminParams['dsn']);
619         else // ADODB or PDO
620             $dsn = parseDSN($AdminParams['dsn']);
621         $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
622                                       $dsn['phptype'],
623                                       DBADMIN_USER,
624                                       DBADMIN_PASSWD,
625                                       $dsn['hostspec'],
626                                       $dsn['database']);
627         if (DEBUG & _DEBUG_SQL and DATABASE_TYPE == 'PDO') {
628             echo "<br>\nDBParams['dsn']: '", $DBParams['dsn'], "'";
629             echo "<br>\ndsn: '", print_r($dsn), "'";
630             echo "<br>\nAdminParams['dsn']: '", $AdminParams['dsn'], "'";
631         }
632         $dbh = WikiDB::open($AdminParams);
633     } elseif ($dbadmin = $request->getArg('dbadmin')) {
634         if (empty($dbadmin['user']) or isset($dbadmin['cancel']))
635             $dbh = &$request->_dbi;
636         else {
637             $AdminParams = $DBParams;
638             if (DATABASE_TYPE == 'SQL')
639                 $dsn = DB::parseDSN($AdminParams['dsn']);
640             else
641                 $dsn = parseDSN($AdminParams['dsn']);
642             $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
643                                       $dsn['phptype'],
644                                       $dbadmin['user'],
645                                       $dbadmin['passwd'],
646                                       $dsn['hostspec'],
647                                       $dsn['database']);
648             $dbh = WikiDB::open($AdminParams);
649         }
650     } else {
651         // Check if the privileges are enough. Need CREATE and ALTER perms. 
652         // And on windows: SELECT FROM mysql, possibly: UPDATE mysql.
653         $form = HTML::form(array("method" => "post", 
654                                  "action" => $request->getPostURL(),
655                                  "accept-charset"=>$GLOBALS['charset']),
656 HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."),
657                                    HTML::br(),
658 _("And on windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")),
659                            HiddenInputs(array('action' => 'upgrade',
660                                               'overwrite' => $request->getArg('overwrite'))),
661                            HTML::table(array("cellspacing"=>4),
662                                        HTML::tr(HTML::td(array('align'=>'right'),
663                                                          _("DB admin user:")),
664                                                 HTML::td(HTML::input(array('name'=>"dbadmin[user]",
665                                                                            'size'=>12,
666                                                                            'maxlength'=>256,
667                                                                            'value'=>'root')))),
668                                        HTML::tr(HTML::td(array('align'=>'right'),
669                                                          _("DB admin password:")),
670                                                 HTML::td(HTML::input(array('name'=>"dbadmin[passwd]",
671                                                                            'type'=>'password',
672                                                                            'size'=>12,
673                                                                            'maxlength'=>256)))),
674                                        HTML::tr(HTML::td(array('align'=>'center', 'colspan' => 2),
675                                                          Button("submit:", _("Submit"), 'wikiaction'), 
676                                                          HTML::raw('&nbsp;'),
677                                                          Button("submit:dbadmin[cancel]", _("Cancel"), 
678                                                                 'button')))));
679         $form->printXml();
680         echo "</div><!-- content -->\n";
681         echo asXML(Template("bottom"));
682         echo "</body></html>\n";
683         $request->finish();
684         exit();
685     }
686 }
687
688 /**
689  * if page.cached_html does not exists:
690  *   put _cached_html from pagedata into a new seperate blob, not huge serialized string.
691  *
692  * it is only rarelely needed: for current page only, if-not-modified
693  * but was extracetd for every simple page iteration.
694  */
695 function _upgrade_cached_html (&$dbh, $verbose=true) {
696     global $DBParams;
697     if (!$dbh->_backend->isSQL()) return;
698     //if (!in_array(DATABASE_TYPE, array('SQL','ADODB','PDO'))) return;
699     $count = 0;
700     if (phpwiki_version() >= 1030.10) {
701         if ($verbose)
702             echo _("check for extra page.cached_html column")," ... ";
703         $database = $dbh->_backend->database();
704         extract($dbh->_backend->_table_names);
705         $fields = $dbh->_backend->listOfFields($database, $page_tbl);
706         if (!$fields) {
707             echo _("SKIP"), "<br />\n";
708             return 0;
709         }
710         if (!strstr(strtolower(join(':', $fields)), "cached_html")) {
711             if ($verbose)
712                 echo "<b>",_("ADDING"),"</b>"," ... ";
713             $backend_type = $dbh->_backend->backendType();
714             if (substr($backend_type,0,5) == 'mysql')
715                 $dbh->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB");
716             else
717                 $dbh->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB");
718             if ($verbose)
719                 echo "<b>",_("CONVERTING"),"</b>"," ... ";
720             $count = _convert_cached_html($dbh);
721             if ($verbose)
722                 echo $count, " ", _("OK"), "<br />\n";
723         } else {
724             if ($verbose)
725                 echo _("OK"), "<br />\n";
726         }
727     }
728     return $count;
729 }
730
731 /** 
732  * move _cached_html for all pages from pagedata into a new seperate blob.
733  * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also.
734  */
735 function _convert_cached_html (&$dbh) {
736     global $DBParams;
737     if (!$dbh->_backend->isSQL()) return;
738     //if (!in_array(DATABASE_TYPE, array('SQL','ADODB'))) return;
739
740     $pages = $dbh->getAllPages();
741     $cache =& $dbh->_cache;
742     $count = 0;
743     extract($dbh->_backend->_table_names);
744     while ($page = $pages->next()) {
745         $pagename = $page->getName();
746         $data = $dbh->_backend->get_pagedata($pagename);
747         if (!empty($data['_cached_html'])) {
748             $cached_html = $data['_cached_html'];
749             $data['_cached_html'] = '';
750             $cache->update_pagedata($pagename, $data);
751             // store as blob, not serialized
752             $dbh->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?",
753                                   array($cached_html, $pagename));
754             $count++;
755         }
756     }
757     return $count;
758 }
759
760 function CheckPluginUpdate(&$request) {
761     echo "<h3>",_("check for necessary plugin argument updates"),"</h3>\n";
762     $process = array('msg' => _("change RandomPage pages => numpages"),
763                      'match' => "/(<\?\s*plugin\s+ RandomPage\s+)pages/",
764                      'replace' => "\\1numpages");
765     $dbi = $request->getDbh();
766     $allpages = $dbi->getAllPages(false);
767     while ($page = $allpages->next()) {
768         $current = $page->getCurrentRevision();
769         $pagetext = $current->getPackedContent();
770         foreach ($process as $p) {
771             if (preg_match($p['match'], $pagetext)) {
772                 echo $page->getName()," ",$p['msg']," ... ";
773                 if ($newtext = preg_replace($p['match'], $p['replace'], $pagetext)) {
774                     $meta = $current->_data;
775                     $meta['summary'] = "upgrade: ".$p['msg'];
776                     $page->save($newtext, $current->getVersion() + 1, $meta);
777                     echo _("OK"), "<br />\n";
778                 } else {
779                     echo " <b><font color=\"red\">", _("FAILED"), "</font></b><br />\n";
780                 }
781             }
782         }
783     }
784 }
785
786 function fixConfigIni($match, $new) {
787     $file = FindFile("config/config.ini");
788     $found = false;
789     if (is_writable($file)) {
790         $in = fopen($file,"rb");
791         $out = fopen($tmp = tempnam(FindFile("uploads"),"cfg"),"wb");
792         if (isWindows())
793             $tmp = str_replace("/","\\",$tmp);
794         while ($s = fgets($in)) {
795             if (preg_match($match, $s)) {
796                 $s = $new . (isWindows() ? "\r\n" : "\n");
797                 $found = true;
798             }
799             fputs($out, $s);
800         }
801         fclose($in);
802         fclose($out);
803         if (!$found) {
804             echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
805                 sprintf(_("%s not found"), $match);
806             unlink($out);
807         } else {
808             @unlink("$file.bak");
809             @rename($file,"$file.bak");
810             if (rename($tmp, $file))
811                 echo " <b>",_("FIXED"),"</b>";
812             else {
813                 echo " <b>",_("FAILED"),"</b>: ";
814                 sprintf(_("couldn't move %s to %s"), $tmp, $file);
815                 return false;
816             }
817         }
818         return $found;
819     } else {
820         echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
821             sprintf(_("%s is not writable"), $file);
822         return false;
823     }
824 }
825
826 function CheckConfigUpdate(&$request) {
827     echo "<h3>",_("check for necessary config updates"),"</h3>\n";
828     echo _("check for old CACHE_CONTROL = NONE")," ... ";
829     if (defined('CACHE_CONTROL') and CACHE_CONTROL == '') {
830         echo "<br />&nbsp;&nbsp;",
831             _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"),
832             " ...";
833         fixConfigIni("/^\s*CACHE_CONTROL\s*=\s*NONE/","CACHE_CONTROL = NO_CACHE");
834     } else {
835         echo _("OK");
836     }
837     echo "<br />\n";
838     echo _("check for GROUP_METHOD = NONE")," ... ";
839     if (defined('GROUP_METHOD') and GROUP_METHOD == '') {
840         echo "<br />&nbsp;&nbsp;",
841             _("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""),
842             " ...";
843         fixConfigIni("/^\s*GROUP_METHOD\s*=\s*NONE/","GROUP_METHOD = \"NONE\"");
844     } else {
845         echo _("OK");
846     }
847     echo "<br />\n";
848 }
849
850 /**
851  * TODO:
852  *
853  * Upgrade: Base class for multipage worksteps
854  * identify, validate, display options, next step
855  */
856 /*
857 class Upgrade {
858 }
859
860 class Upgrade_CheckPgsrc extends Upgrade {
861 }
862
863 class Upgrade_CheckDatabaseUpdate extends Upgrade {
864 }
865 */
866
867 // TODO: At which step are we? 
868 // validate and do it again or go on with next step.
869
870 /** entry function from lib/main.php
871  */
872 function DoUpgrade($request) {
873
874     if (!$request->_user->isAdmin()) {
875         $request->_notAuthorized(WIKIAUTH_ADMIN);
876         $request->finish(
877                          HTML::div(array('class' => 'disabled-plugin'),
878                                    fmt("Upgrade disabled: user != isAdmin")));
879         return;
880     }
881
882     StartLoadDump($request, _("Upgrading this PhpWiki"));
883     //CheckOldIndexUpdate($request); // to upgrade from < 1.3.10
884     CheckDatabaseUpdate($request);   // first check cached_html and friends
885     CheckActionPageUpdate($request);
886     CheckPgsrcUpdate($request);
887     //CheckThemeUpdate($request);
888     //CheckPluginUpdate($request);
889     CheckConfigUpdate($request);
890     EndLoadDump($request);
891 }
892
893
894 /*
895  $Log: not supported by cvs2svn $
896  Revision 1.53  2006/12/03 17:03:18  rurban
897  #1535851 by matt brown
898
899  Revision 1.52  2006/12/03 17:01:18  rurban
900  #1535839 by matt brown
901
902  Revision 1.51  2006/08/07 21:05:30  rurban
903  patch #1535837  (debian)
904
905  Revision 1.50  2006/06/18 11:04:09  rurban
906  keep overwrite arg
907
908  Revision 1.49  2006/05/18 06:03:39  rurban
909  use $dbh->_backend->isSQL
910
911  Revision 1.48  2005/11/14 22:32:38  rurban
912  remove user, SKIP on !session
913
914  Revision 1.47  2005/02/27 19:13:27  rurban
915  latin1 mysql fix
916
917  Revision 1.46  2005/02/12 17:22:18  rurban
918  locale update: missing . : fixed. unified strings
919  proper linebreaks
920
921  Revision 1.45  2005/02/10 19:01:19  rurban
922  add PDO support
923
924  Revision 1.44  2005/02/07 15:40:42  rurban
925  use defined CHARSET for db. more comments
926
927  Revision 1.43  2005/02/04 11:44:07  rurban
928  check passwd in access_log
929
930  Revision 1.42  2005/02/02 19:38:13  rurban
931  prefer utf8 pagenames for collate issues
932
933  Revision 1.41  2005/01/31 12:15:29  rurban
934  print OK
935
936  Revision 1.40  2005/01/30 23:22:17  rurban
937  clarify messages
938
939  Revision 1.39  2005/01/30 23:09:17  rurban
940  sanify session fields
941
942  Revision 1.38  2005/01/25 07:57:02  rurban
943  add dbadmin form, add mysql LOCK TABLES check, add plugin args updater (not yet activated)
944
945  Revision 1.37  2005/01/20 10:19:08  rurban
946  add InterWikiMap to special pages
947
948  Revision 1.36  2004/12/20 12:56:11  rurban
949  patch #1088128 by Kai Krakow. avoid chicken & egg problem
950
951  Revision 1.35  2004/12/13 14:35:41  rurban
952  verbose arg
953
954  Revision 1.34  2004/12/11 09:39:28  rurban
955  needed init for ref
956
957  Revision 1.33  2004/12/10 22:33:39  rurban
958  add WikiAdminUtils method for convert-cached-html
959  missed some vars.
960
961  Revision 1.32  2004/12/10 22:15:00  rurban
962  fix $page->get('_cached_html)
963  refactor upgrade db helper _convert_cached_html() to be able to call them from WikiAdminUtils also.
964  support 2nd genericSqlQuery param (bind huge arg)
965
966  Revision 1.31  2004/12/10 02:45:26  rurban
967  SQL optimization:
968    put _cached_html from pagedata into a new seperate blob, not huge serialized string.
969    it is only rarelely needed: for current page only, if-not-modified
970    but was extracted for every simple page iteration.
971
972  Revision 1.30  2004/11/29 17:58:57  rurban
973  just aesthetics
974
975  Revision 1.29  2004/11/29 16:08:31  rurban
976  added missing nl
977
978  Revision 1.28  2004/11/16 16:25:14  rurban
979  fix accesslog tablename, print CREATED only if really done
980
981  Revision 1.27  2004/11/07 16:02:52  rurban
982  new sql access log (for spam prevention), and restructured access log class
983  dbh->quote (generic)
984  pear_db: mysql specific parts seperated (using replace)
985
986  Revision 1.26  2004/10/14 19:19:34  rurban
987  loadsave: check if the dumped file will be accessible from outside.
988  and some other minor fixes. (cvsclient native not yet ready)
989
990  Revision 1.25  2004/09/06 08:28:00  rurban
991  rename genericQuery to genericSqlQuery
992
993  Revision 1.24  2004/07/05 13:56:22  rurban
994  sqlite autoincrement fix
995
996  Revision 1.23  2004/07/04 10:28:06  rurban
997  DBADMIN_USER fix
998
999  Revision 1.22  2004/07/03 17:21:28  rurban
1000  updated docs: submitted new mysql bugreport (#1491 did not fix it)
1001
1002  Revision 1.21  2004/07/03 16:51:05  rurban
1003  optional DBADMIN_USER:DBADMIN_PASSWD for action=upgrade (if no ALTER permission)
1004  added atomic mysql REPLACE for PearDB as in ADODB
1005  fixed _lock_tables typo links => link
1006  fixes unserialize ADODB bug in line 180
1007
1008  Revision 1.20  2004/07/03 14:48:18  rurban
1009  Tested new mysql 4.1.3-beta: binary search bug as fixed.
1010  => fixed action=upgrade,
1011  => version check in PearDB also (as in ADODB)
1012
1013  Revision 1.19  2004/06/19 12:19:09  rurban
1014  slightly improved docs
1015
1016  Revision 1.18  2004/06/19 11:47:17  rurban
1017  added CheckConfigUpdate: CACHE_CONTROL = NONE => NO_CACHE
1018
1019  Revision 1.17  2004/06/17 11:31:50  rurban
1020  check necessary localized actionpages
1021
1022  Revision 1.16  2004/06/16 10:38:58  rurban
1023  Disallow refernces in calls if the declaration is a reference
1024  ("allow_call_time_pass_reference clean").
1025    PhpWiki is now allow_call_time_pass_reference = Off clean,
1026    but several external libraries may not.
1027    In detail these libs look to be affected (not tested):
1028    * Pear_DB odbc
1029    * adodb oracle
1030
1031  Revision 1.15  2004/06/07 19:50:40  rurban
1032  add owner field to mimified dump
1033
1034  Revision 1.14  2004/06/07 18:38:18  rurban
1035  added mysql 4.1.x search fix
1036
1037  Revision 1.13  2004/06/04 20:32:53  rurban
1038  Several locale related improvements suggested by Pierrick Meignen
1039  LDAP fix by John Cole
1040  reanable admin check without ENABLE_PAGEPERM in the admin plugins
1041
1042  Revision 1.12  2004/05/18 13:59:15  rurban
1043  rename simpleQuery to genericSqlQuery
1044
1045  Revision 1.11  2004/05/15 13:06:17  rurban
1046  skip the HomePage, at first upgrade the ActionPages, then the database, then the rest
1047
1048  Revision 1.10  2004/05/15 01:19:41  rurban
1049  upgrade prefix fix by Kai Krakow
1050
1051  Revision 1.9  2004/05/14 11:33:03  rurban
1052  version updated to 1.3.11pre
1053  upgrade stability fix
1054
1055  Revision 1.8  2004/05/12 10:49:55  rurban
1056  require_once fix for those libs which are loaded before FileFinder and
1057    its automatic include_path fix, and where require_once doesn't grok
1058    dirname(__FILE__) != './lib'
1059  upgrade fix with PearDB
1060  navbar.tmpl: remove spaces for IE &nbsp; button alignment
1061
1062  Revision 1.7  2004/05/06 17:30:38  rurban
1063  CategoryGroup: oops, dos2unix eol
1064  improved phpwiki_version:
1065    pre -= .0001 (1.3.10pre: 1030.099)
1066    -p1 += .001 (1.3.9-p1: 1030.091)
1067  improved InstallTable for mysql and generic SQL versions and all newer tables so far.
1068  abstracted more ADODB/PearDB methods for action=upgrade stuff:
1069    backend->backendType(), backend->database(),
1070    backend->listOfFields(),
1071    backend->listOfTables(),
1072
1073  Revision 1.6  2004/05/03 15:05:36  rurban
1074  + table messages
1075
1076  Revision 1.4  2004/05/02 21:26:38  rurban
1077  limit user session data (HomePageHandle and auth_dbi have to invalidated anyway)
1078    because they will not survive db sessions, if too large.
1079  extended action=upgrade
1080  some WikiTranslation button work
1081  revert WIKIAUTH_UNOBTAINABLE (need it for main.php)
1082  some temp. session debug statements
1083
1084  Revision 1.3  2004/04/29 22:33:30  rurban
1085  fixed sf.net bug #943366 (Kai Krakow)
1086    couldn't load localized url-undecoded pagenames
1087
1088  Revision 1.2  2004/03/12 15:48:07  rurban
1089  fixed explodePageList: wrong sortby argument order in UnfoldSubpages
1090  simplified lib/stdlib.php:explodePageList
1091
1092  */
1093
1094 // For emacs users
1095 // Local Variables:
1096 // mode: php
1097 // tab-width: 8
1098 // c-basic-offset: 4
1099 // c-hanging-comment-ender-p: nil
1100 // indent-tabs-mode: nil
1101 // End:
1102 ?>