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