]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/upgrade.php
get rid of @ error protection in unserialize
[SourceForge/phpwiki.git] / lib / upgrade.php
1 <?php //-*-php-*-
2 rcs_id('$Id: upgrade.php,v 1.61 2008-03-17 19:12:32 rurban Exp $');
3 /*
4  Copyright 2004,2005,2006,2007 $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 existing WikiDB and config settings after installing a new PhpWiki sofwtare version.
25  * Status: almost no queries for verification. 
26  *         simple merge conflict resolution, or Overwrite All.
27  *
28  * Installation on an existing PhpWiki database needs some 
29  * additional worksteps. Each step will require multiple pages.
30  *
31  * This is the plan:
32  *  1. Check for new or changed database schema and update it 
33  *     according to some predefined upgrade tables. (medium, complete)
34  *  2. Check for new or changed (localized) pgsrc/ pages and ask 
35  *     for upgrading these. Check timestamps, upgrade silently or 
36  *     show diffs if existing. Overwrite or merge (easy, complete)
37  *  3. Check for new or changed or deprecated index.php/config.ini settings
38  *     and help in upgrading these. (for newer changes since 1.3.11, not yet)
39  *   3a. Convert old-style index.php into config/config.ini. (easy, not yet)
40  *  4. Check for changed plugin invocation arguments. (medium, done)
41  *  5. Check for changed theme variables. (hard, not yet)
42  *  6. Convert the single-request upgrade to a class-based multi-page 
43  *     version. (hard)
44
45  * Done: overwrite=1 link on edit conflicts at first occurence "Overwrite all".
46  *
47  * @author: Reini Urban
48  */
49 require_once("lib/loadsave.php");
50
51 class Upgrade {
52
53     function Upgrade (&$request) {
54         $this->request =& $request;
55         $this->dbi =& $request->_dbi; // no reference for dbadmin ? 
56         $this->phpwiki_version = $this->current_db_version = phpwiki_version();
57         //$this->current_db_version = 1030.13; // should be stored in the db. should be phpwiki_version
58
59         $this->db_version = $this->dbi->get_db_version();
60         $this->isSQL = $this->dbi->_backend->isSQL();
61     }
62
63     /**
64      * TODO: check for the pgsrc_version number, not the revision mtime only
65      */
66     function doPgsrcUpdate($pagename, $path, $filename) {
67         $page = $this->dbi->getPage($pagename);
68         if ($page->exists()) {
69             // check mtime: update automatically if pgsrc is newer
70             $rev = $page->getCurrentRevision();
71             $page_mtime = $rev->get('mtime');
72             $data  = implode("", file($path."/".$filename));
73             if (($parts = ParseMimeifiedPages($data))) {
74                 usort($parts, 'SortByPageVersion');
75                 reset($parts);
76                 $pageinfo = $parts[0];
77                 $stat  = stat($path."/".$filename);
78                 $new_mtime = 0;
79                 if (isset($pageinfo['versiondata']['mtime']))
80                     $new_mtime = $pageinfo['versiondata']['mtime'];
81                 if (!$new_mtime and isset($pageinfo['versiondata']['lastmodified']))
82                     $new_mtime = $pageinfo['versiondata']['lastmodified'];
83                 if (!$new_mtime and isset($pageinfo['pagedata']['date']))
84                     $new_mtime = $pageinfo['pagedata']['date'];
85                 if (!$new_mtime)
86                     $new_mtime = $stat[9];
87                 if ($new_mtime > $page_mtime) {
88                     echo "$path/$pagename: ",_("newer than the existing page."),
89                         _(" replace "),"($new_mtime &gt; $page_mtime)","<br />\n";
90                     LoadAny($this->request, $path."/".$filename);
91                     echo "<br />\n";
92                 } else {
93                     echo "$path/$pagename: ",_("older than the existing page."),
94                         _(" skipped"),".<br />\n";
95                 }
96             } else {
97                 echo "$path/$pagename: ",("unknown format."),
98                     _(" skipped"),".<br />\n";
99             }
100         } else {
101             echo sprintf(_("%s does not exist"),$pagename),"<br />\n";
102             LoadAny($this->request, $path."/".$filename);
103             echo "<br />\n";
104         }
105     }
106
107     /** 
108      *  If a matching pgsrc => pluginname exists
109      *  Need the english filename (required precondition: urlencode == urldecode).
110      *  Returns the plugin name.
111      */ 
112     function isActionPage($filename) {
113         static $special = array("DebugInfo"     => "_BackendInfo",
114                                 "PhpWikiRecentChanges" => "RssFeed",
115                                 "ProjectSummary"        => "RssFeed",
116                                 "RecentReleases"        => "RssFeed",
117                                 "InterWikiMap"      => "InterWikiMap",
118                                 );
119         $base = preg_replace("/\..{1,4}$/","",basename($filename));
120         if (isset($special[$base])) return $special[$base];
121         if (FindFile("lib/plugin/".$base.".php",true)) return $base;
122         else return false;
123     }
124
125     function CheckActionPageUpdate() {
126         echo "<h3>",sprintf(_("check for necessary %s updates"),
127                             _("ActionPage")),"</h3>\n";
128         // 1.3.13 before we pull in all missing pages, we rename existing ones
129         $this->_rename_page_helper(_("_AuthInfo"), _("DebugAuthInfo"));
130         // this is in some templates. so we keep the old name
131         //$this->_rename_page_helper($this->dbi, _("DebugInfo"), _("DebugBackendInfo")); 
132         $this->_rename_page_helper(_("_GroupInfo"), _("GroupAuthInfo")); //never officially existed
133         $this->_rename_page_helper("InterWikiKarte", "InterWikiListe"); // german only
134  
135         $path = FindFile('pgsrc');
136         $pgsrc = new fileSet($path);
137         // most actionpages have the same name as the plugin
138         $loc_path = FindLocalizedFile('pgsrc');
139         foreach ($pgsrc->getFiles() as $filename) {
140             if (substr($filename,-1,1) == '~') continue;
141             if (substr($filename,-5,5) == '.orig') continue;
142             $pagename = urldecode($filename);
143             if ($this->isActionPage($filename)) {
144                 $translation = gettext($pagename);
145                 if ($translation == $pagename)
146                     $this->doPgsrcUpdate($pagename, $path, $filename);
147                 elseif (FindLocalizedFile('pgsrc/'.urlencode($translation),1))
148                     $this->doPgsrcUpdate($translation, $loc_path, urlencode($translation));
149                 else
150                     $this->doPgsrcUpdate($pagename, $path, $filename);
151             }
152         }
153     }
154
155     // see loadsave.php for saving new pages.
156     function CheckPgsrcUpdate() {
157         echo "<h3>",sprintf(_("check for necessary %s updates"),
158                             "pgsrc"),"</h3>\n";
159         if ($this->db_version < 1030.12200612) {
160             echo "<h4>",_("rename to Help: pages"),"</h4>\n";
161         }
162         $path = FindLocalizedFile(WIKI_PGSRC);
163         $pgsrc = new fileSet($path);
164         // fixme: verification, ...
165         $isHomePage = false;
166         foreach ($pgsrc->getFiles() as $filename) {
167             if (substr($filename,-1,1) == '~') continue;
168             if (substr($filename,-5,5) == '.orig') continue;
169             $pagename = urldecode($filename);
170             // don't ever update the HomePage
171             if (defined(HOME_PAGE))
172                 if ($pagename == HOME_PAGE) $isHomePage = true;
173                 else
174                     if ($pagename == _("HomePage")) $isHomePage = true;
175             if ($pagename == "HomePage") $isHomePage = true;
176             if ($isHomePage) {
177                 echo "$path/$pagename: ",_("always skip the HomePage."),
178                     _(" skipped"),".<br />\n";
179                 $isHomePage = false;
180                 continue;
181             }
182             if (!$this->isActionPage($filename)) {
183                 // There're a lot of now unneeded pages around. 
184                 // At first rename the BlaPlugin pages to Help/<pagename> and then to the update.
185                 if ($this->db_version < 1030.12200612) {
186                     $this->_rename_to_help_page($pagename);    
187                 }
188                 $this->doPgsrcUpdate($pagename,$path,$filename);
189             }
190         }
191
192         // Now check some theme specific pgsrc files (blog, wikilens, custom). 
193         // WARNING: Also override the HomePage here.
194         global $WikiTheme;
195         $path = $WikiTheme->file("pgsrc");
196         $pgsrc = new fileSet($path);
197         if ($pgsrc->getFiles()) {
198             echo "<h3>",sprintf(_("check for additional theme %s updates"),
199                                 "pgsrc"),"</h3>\n";
200             foreach ($pgsrc->getFiles() as $filename) {
201                 if (substr($filename,-1,1) == '~') continue;
202                 if (substr($filename,-5,5) == '.orig') continue;
203                 $pagename = urldecode($filename);
204                 $this->doPgsrcUpdate($pagename,$path,$filename);
205             }
206         }
207         return;
208     }
209
210     function _rename_page_helper($oldname, $pagename) {
211         echo sprintf(_("rename %s to %s"), $oldname, $pagename)," ...";
212         if ($this->dbi->isWikiPage($oldname) and !$this->dbi->isWikiPage($pagename)) {
213             if ($this->dbi->_backend->rename_page($oldname, $pagename))
214                 echo _("OK")," <br />\n";
215             else
216                 echo " <b><font color=\"red\">", _("FAILED"), "</font></b>",
217                     " <br />\n";
218         } else {
219             echo _(" skipped")," <br />\n";
220         }
221     }
222
223     function _rename_to_help_page($pagename) {
224         $newprefix = _("Help") . "/";
225         if (substr($pagename,0,strlen($newprefix)) != $newprefix) return;       
226         $oldname = substr($pagename,strlen($newprefix));
227         $this->_rename_page_helper($oldname, $pagename);
228     }
229
230     /**
231      * TODO: Search table definition in appropriate schema
232      *       and create it.
233      * Supported: mysql and generic SQL, for ADODB and PearDB.
234      */
235     function installTable($table, $backend_type) {
236         global $DBParams;
237         if (!$this->isSQL) return;
238         echo _("MISSING")," ... \n";
239         $backend = &$this->dbi->_backend->_dbh;
240         /*
241           $schema = findFile("schemas/${backend_type}.sql");
242           if (!$schema) {
243           echo "  ",_("FAILED"),": ",sprintf(_("no schema %s found"),
244           "schemas/${backend_type}.sql")," ... <br />\n";
245           return false;
246           }
247         */
248         extract($this->dbi->_backend->_table_names);
249         $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
250         switch ($table) {
251         case 'session':
252             assert($session_tbl);
253             if ($backend_type == 'mysql') {
254                 $this->dbi->genericSqlQuery("
255 CREATE TABLE $session_tbl (
256         sess_id         CHAR(32) NOT NULL DEFAULT '',
257         sess_data       BLOB NOT NULL,
258         sess_date       INT UNSIGNED NOT NULL,
259         sess_ip         CHAR(15) NOT NULL,
260         PRIMARY KEY (sess_id),
261         INDEX (sess_date)
262 )");
263             } else {
264                 $this->dbi->genericSqlQuery("
265 CREATE TABLE $session_tbl (
266         sess_id         CHAR(32) NOT NULL DEFAULT '',
267         sess_data       ".($backend_type == 'pgsql'?'TEXT':'BLOB')." NOT NULL,
268         sess_date       INT,
269         sess_ip         CHAR(15) NOT NULL
270 )");
271                 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
272             }
273             $this->dbi->genericSqlQuery("CREATE INDEX sess_date on session (sess_date)");
274             echo "  ",_("CREATED");
275             break;
276         case 'pref':
277             $pref_tbl = $prefix.'pref';
278             if ($backend_type == 'mysql') {
279                 $this->dbi->genericSqlQuery("
280 CREATE TABLE $pref_tbl (
281         userid  CHAR(48) BINARY NOT NULL UNIQUE,
282         prefs   TEXT NULL DEFAULT '',
283         PRIMARY KEY (userid)
284 )");
285             } else {
286                 $this->dbi->genericSqlQuery("
287 CREATE TABLE $pref_tbl (
288         userid  CHAR(48) NOT NULL,
289         prefs   TEXT NULL DEFAULT ''
290 )");
291                 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
292             }
293             echo "  ",_("CREATED");
294             break;
295         case 'member':
296             $member_tbl = $prefix.'member';
297             if ($backend_type == 'mysql') {
298                 $this->dbi->genericSqlQuery("
299 CREATE TABLE $member_tbl (
300         userid    CHAR(48) BINARY NOT NULL,
301         groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
302         INDEX (userid),
303         INDEX (groupname)
304 )");
305             } else {
306                 $this->dbi->genericSqlQuery("
307 CREATE TABLE $member_tbl (
308         userid    CHAR(48) NOT NULL,
309         groupname CHAR(48) NOT NULL DEFAULT 'users'
310 )");
311                 $this->dbi->genericSqlQuery("CREATE INDEX userid ON $member_tbl (userid)");
312                 $this->dbi->genericSqlQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
313             }
314             echo "  ",_("CREATED");
315             break;
316         case 'rating':
317             $rating_tbl = $prefix.'rating';
318             if ($backend_type == 'mysql') {
319                 $this->dbi->genericSqlQuery("
320 CREATE TABLE $rating_tbl (
321         dimension INT(4) NOT NULL,
322         raterpage INT(11) NOT NULL,
323         rateepage INT(11) NOT NULL,
324         ratingvalue FLOAT NOT NULL,
325         rateeversion INT(11) NOT NULL,
326         tstamp TIMESTAMP(14) NOT NULL,
327         PRIMARY KEY (dimension, raterpage, rateepage)
328 )");
329             } else {
330                 $this->dbi->genericSqlQuery("
331 CREATE TABLE $rating_tbl (
332         dimension INT(4) NOT NULL,
333         raterpage INT(11) NOT NULL,
334         rateepage INT(11) NOT NULL,
335         ratingvalue FLOAT NOT NULL,
336         rateeversion INT(11) NOT NULL,
337         tstamp TIMESTAMP(14) NOT NULL
338 )");
339                 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX rating"
340                                       ." ON $rating_tbl (dimension, raterpage, rateepage)");
341             }
342             echo "  ",_("CREATED");
343             break;
344         case 'accesslog':
345             $log_tbl = $prefix.'accesslog';
346             // fields according to http://www.outoforder.cc/projects/apache/mod_log_sql/docs-2.0/#id2756178
347             /*
348               A User Agent agent        varchar(255)    Mozilla/4.0 (compat; MSIE 6.0; Windows)
349               a CGi request arguments   request_args    varchar(255)    user=Smith&cart=1231&item=532
350               b Bytes transfered        bytes_sent      int unsigned    32561
351               c???      Text of cookie  cookie  varchar(255)    Apache=sdyn.fooonline.net 1300102700823
352               f Local filename requested        request_file    varchar(255)    /var/www/html/books-cycroad.html
353               H HTTP request_protocol   request_protocol        varchar(10)     HTTP/1.1
354               h Name of remote host     remote_host     varchar(50)     blah.foobar.com
355               I Request ID (from modd_unique_id)        id      char(19)        POlFcUBRH30AAALdBG8
356               l Ident user info remote_logname  varcgar(50)     bobby
357               M Machine ID???   machine_id      varchar(25)     web01
358               m HTTP request method     request_method  varchar(10)     GET
359               P httpd cchild PID        child_pid       smallint unsigned       3215
360               p http port       server_port     smallint unsigned       80
361               R Referer referer varchar(255)    http://www.biglinks4u.com/linkpage.html
362               r Request in full form    request_line    varchar(255)    GET /books-cycroad.html HTTP/1.1
363               S Time of request in UNIX time_t format   time_stamp      int unsigned    1005598029
364               T Seconds to service request      request_duration        smallint unsigned       2
365               t Time of request in human format request_time    char(28)        [02/Dec/2001:15:01:26 -0800]
366               U Request in simple form  request_uri     varchar(255)    /books-cycroad.html
367               u User info from HTTP auth        remote_user     varchar(50)     bobby
368               v Virtual host servicing the request      virtual_host    varchar(255)
369             */
370             $this->dbi->genericSqlQuery("
371 CREATE TABLE $log_tbl (
372         time_stamp    int unsigned,
373         remote_host   varchar(100),
374         remote_user   varchar(50),
375         request_method varchar(10),
376         request_line  varchar(255),
377         request_args  varchar(255),
378         request_uri   varchar(255),
379         request_time  char(28),
380         status        smallint unsigned,
381         bytes_sent    smallint unsigned,
382         referer       varchar(255), 
383         agent         varchar(255),
384         request_duration float
385 )");
386             $this->dbi->genericSqlQuery("CREATE INDEX log_time ON $log_tbl (time_stamp)");
387             $this->dbi->genericSqlQuery("CREATE INDEX log_host ON $log_tbl (remote_host)");
388             echo "  ",_("CREATED");
389             break;
390         }
391         echo "<br />\n";
392     }
393
394     /**
395      * Update from ~1.3.4 to current.
396      * tables: Only session, user, pref and member
397      * jeffs-hacks database api (around 1.3.2) later:
398      *   people should export/import their pages if using that old versions.
399      */
400     function CheckDatabaseUpdate() {
401         global $DBAuthParams, $DBParams;
402
403         echo "<h3>",sprintf(_("check for necessary %s updates"),
404                             _("database")),
405             " - ", DATABASE_TYPE,"</h3>\n";
406         $dbadmin = $this->request->getArg('dbadmin');
407         if ($this->isSQL) {
408             $this->_db_init();
409             if (isset($dbadmin['cancel'])) {
410                 echo _("CANCEL")," <br />\n";
411                 return;
412             }
413         }
414         echo "db version: we want ", $this->current_db_version, "\n<br>";
415         echo "db version: we have ", $this->db_version, "\n<br>";
416         if ($this->db_version >= $this->current_db_version) {
417             echo _("OK"), "<br />\n";
418             return;
419         }
420
421         $backend_type = $this->dbi->_backend->backendType();
422         if ($this->isSQL) {
423             echo "<h4>",_("Backend type: "),$backend_type,"</h4>\n";
424             $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
425             $tables = $this->dbi->_backend->listOfTables();
426             foreach (explode(':','session:pref:member') as $table) {
427                 echo sprintf(_("check for table %s"), $table)," ...";
428                 if (!in_array($prefix.$table, $tables)) {
429                     $this->installTable($table, $backend_type);
430                 } else {
431                     echo _("OK")," <br />\n";
432                 }
433             }
434         }
435
436         if ($this->phpwiki_version >= 1030.12200612 and $this->db_version < 1030.13) {
437             if ($this->isSQL and preg_match("/(pgsql|postgres)/", $backend_type)) {
438                 trigger_error("You need to upgrade to schema/psql-initialize.sql manually!", 
439                               E_USER_WARNING);
440                 // $this->_upgrade_psql_tsearch2();
441             }
442             $this->_upgrade_relation_links();
443         }
444
445         if (ACCESS_LOG_SQL and $this->isSQL) {
446             $table = "accesslog";
447             echo sprintf(_("check for table %s"), $table)," ...";
448             if (!in_array($prefix.$table, $tables)) {
449                 $this->installTable($table, $backend_type);
450             } else {
451                 echo _("OK")," <br />\n";
452             }
453         }
454         if ($this->isSQL and (class_exists("RatingsUserFactory") or $this->dbi->isWikiPage(_("RateIt")))) {
455             $table = "rating";
456             echo sprintf(_("check for table %s"), $table)," ...";
457             if (!in_array($prefix.$table, $tables)) {
458                 $this->installTable($table, $backend_type);
459             } else {
460                 echo _("OK")," <br />\n";
461             }
462         }
463         $backend = &$this->dbi->_backend->_dbh;
464         if ($this->isSQL)
465             extract($this->dbi->_backend->_table_names);
466
467         // 1.3.8 added session.sess_ip
468         if ($this->isSQL and $this->phpwiki_version >= 1030.08 and USE_DB_SESSION 
469             and isset($this->request->_dbsession)) 
470         {
471             echo _("check for new session.sess_ip column")," ... ";
472             $database = $this->dbi->_backend->database();
473             assert(!empty($DBParams['db_session_table']));
474             $session_tbl = $prefix . $DBParams['db_session_table'];
475             $sess_fields = $this->dbi->_backend->listOfFields($database, $session_tbl);
476             if (!$sess_fields) {
477                 echo _("SKIP");
478             } elseif (!strstr(strtolower(join(':', $sess_fields)), "sess_ip")) {
479                 // TODO: postgres test (should be able to add columns at the end, but not in between)
480                 echo "<b>",_("ADDING"),"</b>"," ... ";          
481                 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
482                 $this->dbi->genericSqlQuery("CREATE INDEX sess_date ON $session_tbl (sess_date)");
483             } else {
484                 echo _("OK");
485             }
486             echo "<br />\n";
487             if (substr($backend_type,0,5) == 'mysql') {
488                 // upgrade to 4.1.8 destroyed my session table: 
489                 // sess_id => varchar(10), sess_data => varchar(5). For others obviously also.
490                 echo _("check for mysql session.sess_id sanity")," ... ";
491                 $result = $this->dbi->genericSqlQuery("DESCRIBE $session_tbl");
492                 if (DATABASE_TYPE == 'SQL') {
493                     $iter = new WikiDB_backend_PearDB_generic_iter($backend, $result);
494                 } elseif (DATABASE_TYPE == 'ADODB') {
495                     $iter = new WikiDB_backend_ADODB_generic_iter($backend, $result, 
496                                                                   array("Field", "Type", "Null", "Key", "Default", "Extra"));
497                 } elseif (DATABASE_TYPE == 'PDO') {
498                     $iter = new WikiDB_backend_PDO_generic_iter($backend, $result);
499                 }
500                 while ($col = $iter->next()) {
501                     if ($col["Field"] == 'sess_id' and !strstr(strtolower($col["Type"]), 'char(32)')) {
502                         $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_id"
503                                               ." sess_id CHAR(32) NOT NULL");
504                         echo "sess_id ", $col["Type"], " ", _("fixed"), " =&gt; CHAR(32) ";
505                     }
506                     if ($col["Field"] == 'sess_ip' and !strstr(strtolower($col["Type"]), 'char(15)')) {
507                         $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_ip"
508                                               ." sess_ip CHAR(15) NOT NULL");
509                         echo "sess_ip ", $col["Type"], " ", _("fixed"), " =&gt; CHAR(15) ";
510                     }
511                 }
512                 echo _("OK"), "<br />\n";
513             }
514         }
515
516         /* TODO:
517            ALTER TABLE link ADD relation INT DEFAULT 0;
518            CREATE INDEX linkrelation ON link (relation);
519         */
520
521         // mysql >= 4.0.4 requires LOCK TABLE privileges
522         if (substr($backend_type,0,5) == 'mysql') {
523             echo _("check for mysql LOCK TABLE privilege")," ...";
524             $mysql_version = $this->dbi->_backend->_serverinfo['version'];
525             if ($mysql_version > 400.40) {
526                 if (!empty($this->dbi->_backend->_parsedDSN))
527                     $parseDSN = $this->dbi->_backend->_parsedDSN;
528                 elseif (function_exists('parseDSN')) // ADODB or PDO
529                     $parseDSN = parseDSN($DBParams['dsn']);
530                 else                         // pear
531                     $parseDSN = DB::parseDSN($DBParams['dsn']);
532                 $username = $this->dbi->_backend->qstr($parseDSN['username']);
533                 // on db level
534                 $query = "SELECT lock_tables_priv FROM mysql.db WHERE user='$username'";
535                 //mysql_select_db("mysql", $this->dbi->_backend->connection());
536                 $db_fields = $this->dbi->_backend->listOfFields("mysql", "db");
537                 if (!strstr(strtolower(join(':', $db_fields)), "lock_tables_priv")) {
538                     echo join(':', $db_fields);
539                     die("lock_tables_priv missing. The DB Admin must run mysql_fix_privilege_tables");
540                 }
541                 $row = $this->dbi->_backend->getRow($query);
542                 if (isset($row[0]) and $row[0] == 'N') {
543                     $this->dbi->genericSqlQuery("UPDATE mysql.db SET lock_tables_priv='Y'"
544                                           ." WHERE mysql.user='$username'");
545                     $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
546                     echo "mysql.db user='$username'", _("fixed"), "<br />\n";
547                 } elseif (!$row) {
548                     // or on user level
549                     $query = "SELECT lock_tables_priv FROM mysql.user WHERE user='$username'";
550                     $row = $this->dbi->_backend->getRow($query);
551                     if ($row and $row[0] == 'N') {
552                         $this->dbi->genericSqlQuery("UPDATE mysql.user SET lock_tables_priv='Y'"
553                                               ." WHERE mysql.user='$username'");
554                         $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
555                         echo "mysql.user user='$username'", _("fixed"), "<br />\n";
556                     } elseif (!$row) {
557                         echo " <b><font color=\"red\">", _("FAILED"), "</font></b>: ",
558                             "Neither mysql.db nor mysql.user has a user='$username'"
559                             ." or the lock_tables_priv field",
560                             "<br />\n";
561                     } else {
562                         echo _("OK"), "<br />\n";
563                     }
564                 } else {
565                     echo _("OK"), "<br />\n";
566                 }
567                 //mysql_select_db($this->dbi->_backend->database(), $this->dbi->_backend->connection());
568             } else {
569                 echo sprintf(_("version <em>%s</em> not affected"), $mysql_version),"<br />\n";
570             }
571         }
572
573         // 1.3.10 mysql requires page.id auto_increment
574         // mysql, mysqli or mysqlt
575         if ($this->phpwiki_version >= 1030.099 and substr($backend_type,0,5) == 'mysql' 
576             and DATABASE_TYPE != 'PDO') 
577         {
578             echo _("check for mysql page.id auto_increment flag")," ...";
579             assert(!empty($page_tbl));
580             $database = $this->dbi->_backend->database();
581             $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
582             $columns = mysql_num_fields($fields); 
583             for ($i = 0; $i < $columns; $i++) {
584                 if (mysql_field_name($fields, $i) == 'id') {
585                     $flags = mysql_field_flags($fields, $i);
586                     //DONE: something was wrong with ADODB here.
587                     if (!strstr(strtolower($flags), "auto_increment")) {
588                         echo "<b>",_("ADDING"),"</b>"," ... ";
589                         // MODIFY col_def valid since mysql 3.22.16,
590                         // older mysql's need CHANGE old_col col_def
591                         $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE id"
592                                                     ." id INT NOT NULL AUTO_INCREMENT");
593                         $fields = mysql_list_fields($database, $page_tbl);
594                         if (!strstr(strtolower(mysql_field_flags($fields, $i)), "auto_increment"))
595                             echo " <b><font color=\"red\">", _("FAILED"), "</font></b><br />\n";
596                         else     
597                             echo _("OK"), "<br />\n";
598                     } else {
599                         echo _("OK"), "<br />\n";
600                     }
601                     break;
602                 }
603             }
604             mysql_free_result($fields);
605         }
606
607         // Check for mysql 4.1.x/5.0.0a binary search problem.
608         //   http://bugs.mysql.com/bug.php?id=4398
609         // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
610         // Confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
611         // On windows only, though utf8 would be useful elsewhere also.
612         // Illegal mix of collations (latin1_bin,IMPLICIT) and 
613         // (utf8_general_ci, COERCIBLE) for operation '='])
614         if (isWindows() and substr($backend_type,0,5) == 'mysql') {
615             echo _("check for mysql 4.1.x/5.0.0 binary search on windows problem")," ...";
616             $mysql_version = $this->dbi->_backend->_serverinfo['version'];
617             if ($mysql_version < 401.0) { 
618                 echo sprintf(_("version <em>%s</em>"), $mysql_version)," ",
619                     _("not affected"),"<br />\n";
620             } elseif ($mysql_version >= 401.6) { // FIXME: since which version?
621                 $row = $this->dbi->_backend->getRow("SHOW CREATE TABLE $page_tbl");
622                 $result = join(" ", $row);
623                 if (strstr(strtolower($result), "character set") 
624                     and strstr(strtolower($result), "collate")) 
625                     {
626                         echo _("OK"), "<br />\n";
627                     } else {
628                     //SET CHARACTER SET latin1
629                     $charset = CHARSET;
630                     if ($charset == 'iso-8859-1') $charset = 'latin1';
631                     $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename "
632                                           ."pagename VARCHAR(100) "
633                                           ."CHARACTER SET '$charset' COLLATE '$charset"."_bin' NOT NULL");
634                     echo sprintf(_("version <em>%s</em>"), $mysql_version), 
635                         " <b>",_("FIXED"),"</b>",
636                         "<br />\n";
637                 }
638             } elseif (DATABASE_TYPE != 'PDO') {
639                 // check if already fixed
640                 extract($this->dbi->_backend->_table_names);
641                 assert(!empty($page_tbl));
642                 $database = $this->dbi->_backend->database();
643                 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
644                 $columns = mysql_num_fields($fields); 
645                 for ($i = 0; $i < $columns; $i++) {
646                     if (mysql_field_name($fields, $i) == 'pagename') {
647                         $flags = mysql_field_flags($fields, $i);
648                         // I think it was fixed with 4.1.6, but I tested it only with 4.1.8
649                         if ($mysql_version > 401.0 and $mysql_version < 401.6) {
650                             // remove the binary flag
651                             if (strstr(strtolower($flags), "binary")) {
652                                 // FIXME: on duplicate pagenames this will fail!
653                                 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename"
654                                                       ." pagename VARCHAR(100) NOT NULL");
655                                 echo sprintf(_("version <em>%s</em>"), $mysql_version), 
656                                     "<b>",_("FIXED"),"</b>"
657                                     ,"<br />\n";        
658                             }
659                         }
660                         break;
661                     }
662                 }
663             }
664         }
665         if ($this->isSQL and ACCESS_LOG_SQL & 2) {
666             echo _("check for ACCESS_LOG_SQL passwords in POST requests")," ...";
667             // Don't display passwords in POST requests (up to 2005-02-04 12:03:20)
668             $res = $this->dbi->genericSqlIter("SELECT time_stamp, remote_host, " .
669                                         "request_args FROM ${prefix}accesslog WHERE request_args LIKE " .
670                                         "'%s:6:\"passwd\"%' AND request_args NOT LIKE '%s:6:\"passwd\";" .
671                                         "s:15:\"<not displayed>\"%'");
672             $count = 0;
673             while ($row = $res->next()) {
674                 $args = preg_replace("/(s:6:\"passwd\";s:15:\").*(\")/", 
675                                      "$1<not displayed>$2", $row["request_args"]);
676                 $ts = $row["time_stamp"];
677                 $rh = $row["remote_host"];
678                 $this->dbi->genericSqlQuery("UPDATE ${prefix}accesslog SET " .
679                                       "request_args='$args' WHERE time_stamp=$ts AND " .
680                                       "remote_host='$rh'");
681                 $count++;
682             }
683             if ($count > 0)
684                 echo "<b>",_("FIXED"),"</b>", "<br />\n";
685             else 
686                 echo _("OK"),"<br />\n";
687
688             if ($this->phpwiki_version >= 1030.13) {
689                 echo _("check for ACCESS_LOG_SQL remote_host varchar(50)")," ...";
690                 $database = $this->dbi->_backend->database();
691                 $accesslog_tbl = $prefix . 'accesslog';
692                 $fields = $this->dbi->_backend->listOfFields($database, $accesslog_tbl);
693                 if (!$fields) {
694                     echo _("SKIP");
695                 } elseif (strstr(strtolower(join(':', $sess_fields)), "remote_host")) {
696                     // TODO: how to check size, already done?
697                     echo "<b>",_("FIXING"),"remote_host</b>"," ... ";
698                     $this->dbi->genericSqlQuery("ALTER TABLE $accesslog_tbl CHANGE remote_host VARCHAR(100)");
699                 } else {
700                     echo _("FAIL");
701                 }
702                 echo "<br />\n";
703             }
704         }
705         $this->_upgrade_cached_html();
706
707         if ($this->db_version < $this->current_db_version) {
708             $this->dbi->set_db_version($this->current_db_version);
709             $this->db_version = $this->dbi->get_db_version();
710             echo "db version: upgrade to ", $this->db_version,"  ";
711             echo _("OK"), "<br />\n";
712             flush();
713         }
714
715         return;
716     }
717
718     /**
719      * Filter SQL missing permissions errors.
720      *
721      * A wrong DBADMIN user will not be able to connect
722      * @see _is_false_error, ErrorManager
723      * @access private
724      */
725     function _dbpermission_filter($err) {
726         if  ( $err->isWarning() ) {
727             global $ErrorManager;
728             $this->error_caught = 1;
729             $ErrorManager->_postponed_errors[] = $err;
730             return true;
731         }
732         return false;
733     }
734
735     function _try_dbadmin_user ($user, $passwd) {
736         global $DBParams, $DBAuthParams;
737         $AdminParams = $DBParams;
738         if (DATABASE_TYPE == 'SQL')
739             $dsn = DB::parseDSN($AdminParams['dsn']);
740         else {
741             $dsn = parseDSN($AdminParams['dsn']);
742         }
743         $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
744                                       $dsn['phptype'],
745                                       $user,
746                                       $passwd,
747                                       $dsn['hostspec'],
748                                       $dsn['database']);
749         $AdminParams['_tryroot_from_upgrade'] = 1;
750         // add error handler to warn about missing permissions for DBADMIN_USER
751         global $ErrorManager;
752         $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_dbpermission_filter'));
753         $this->error_caught = 0;
754         $this->dbi = WikiDB::open($AdminParams);
755         if (!$this->error_caught) return true; 
756         // FAILED: redo our connection with the wikiuser
757         $this->dbi = WikiDB::open($DBParams);
758         $ErrorManager->flushPostponedErrors();
759         $ErrorManager->popErrorHandler();
760         return false;
761     }
762
763     function _db_init () {
764         if (!$this->isSQL) return;
765
766         /* SQLite never needs admin params */
767         $backend_type = $this->dbi->_backend->backendType();
768         if (substr($backend_type,0,6)=="sqlite") {
769             return;
770         }
771         $dbadmin_user = 'root';
772         if ($dbadmin = $this->request->getArg('dbadmin')) {
773             $dbadmin_user = $dbadmin['user'];
774             if (isset($dbadmin['cancel'])) {
775                 return;
776             } elseif (!empty($dbadmin_user)) {
777                 if ($this->_try_dbadmin_user($dbadmin['user'], $dbadmin['passwd']))
778                     return;
779             }
780         } elseif (DBADMIN_USER) {
781             if ($this->_try_dbadmin_user(DBADMIN_USER, DBADMIN_PASSWD))
782                 return true;
783         }
784         // Check if the privileges are enough. Need CREATE and ALTER perms. 
785         // And on windows: SELECT FROM mysql, possibly: UPDATE mysql.
786         $form = HTML::form(array("method" => "post", 
787                                  "action" => $this->request->getPostURL(),
788                                  "accept-charset"=>$GLOBALS['charset']),
789                            HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."),
790                                    HTML::br(),
791                                    _("And on windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")),
792                            HiddenInputs(array('action' => 'upgrade',
793                                               'overwrite' => $this->request->getArg('overwrite'))),
794                            HTML::table(array("cellspacing"=>4),
795                                        HTML::tr(HTML::td(array('align'=>'right'),
796                                                          _("DB admin user:")),
797                                                 HTML::td(HTML::input(array('name'=>"dbadmin[user]",
798                                                                            'size'=>12,
799                                                                            'maxlength'=>256,
800                                                                            'value'=>$dbadmin_user)))),
801                                        HTML::tr(HTML::td(array('align'=>'right'),
802                                                          _("DB admin password:")),
803                                                 HTML::td(HTML::input(array('name'=>"dbadmin[passwd]",
804                                                                            'type'=>'password',
805                                                                            'size'=>12,
806                                                                            'maxlength'=>256)))),
807                                        HTML::tr(HTML::td(array('align'=>'center', 'colspan' => 2),
808                                                          Button("submit:", _("Submit"), 'wikiaction'), 
809                                                          HTML::raw('&nbsp;'),
810                                                          Button("submit:dbadmin[cancel]", _("Cancel"), 
811                                                                 'button')))));
812         $form->printXml();
813         echo "</div><!-- content -->\n";
814         echo asXML(Template("bottom"));
815         echo "</body></html>\n";
816         $this->request->finish();
817         exit();
818     }
819
820     /**
821      * if page.cached_html does not exists:
822      *   put _cached_html from pagedata into a new seperate blob, 
823      *   not into the huge serialized string.
824      *
825      * It is only rarelely needed: for current page only, if-not-modified,
826      * but was extracetd for every simple page iteration.
827      */
828     function _upgrade_cached_html ( $verbose=true ) {
829         global $DBParams;
830         if (!$this->isSQL) return;
831         $count = 0;
832         if ($this->phpwiki_version >= 1030.10) {
833             if ($verbose)
834                 echo _("check for extra page.cached_html column")," ... ";
835             $database = $this->dbi->_backend->database();
836             extract($this->dbi->_backend->_table_names);
837             $fields = $this->dbi->_backend->listOfFields($database, $page_tbl);
838             if (!$fields) {
839                 echo _("SKIP"), "<br />\n";
840                 return 0;
841             }
842             if (!strstr(strtolower(join(':', $fields)), "cached_html")) {
843                 if ($verbose)
844                     echo "<b>",_("ADDING"),"</b>"," ... ";
845                 $backend_type = $this->dbi->_backend->backendType();
846                 if (substr($backend_type,0,5) == 'mysql')
847                     $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB");
848                 else
849                     $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB");
850                 if ($verbose)
851                     echo "<b>",_("CONVERTING"),"</b>"," ... ";
852                 $count = _convert_cached_html();
853                 if ($verbose)
854                     echo $count, " ", _("OK"), "<br />\n";
855             } else {
856                 if ($verbose)
857                     echo _("OK"), "<br />\n";
858             }
859         }
860         return $count;
861     }
862
863     /** 
864      * move _cached_html for all pages from pagedata into a new seperate blob.
865      * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also.
866      */
867     function _convert_cached_html () {
868         global $DBParams;
869         if (!$this->isSQL) return;
870         //if (!in_array(DATABASE_TYPE, array('SQL','ADODB'))) return;
871
872         $pages = $this->dbi->getAllPages();
873         $cache =& $this->dbi->_cache;
874         $count = 0;
875         extract($this->dbi->_backend->_table_names);
876         while ($page = $pages->next()) {
877             $pagename = $page->getName();
878             $data = $this->dbi->_backend->get_pagedata($pagename);
879             if (!empty($data['_cached_html'])) {
880                 $cached_html = $data['_cached_html'];
881                 $data['_cached_html'] = '';
882                 $cache->update_pagedata($pagename, $data);
883                 // store as blob, not serialized
884                 $this->dbi->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?",
885                                       array($cached_html, $pagename));
886                 $count++;
887             }
888         }
889         return $count;
890     }
891
892     /**
893      * upgrade to 1.3.13 link structure.
894      */
895     function _upgrade_relation_links ( $verbose=true ) {
896         if ($this->phpwiki_version >= 1030.12200610 and $this->isSQL) {
897             echo _("check for relation field in link table")," ...";
898             $database = $this->dbi->_backend->database();
899             $link_tbl = $prefix . 'link';
900             $fields = $this->dbi->_backend->listOfFields($database, $link_tbl);
901             if (!$fields) {
902                 echo _("SKIP");
903             } elseif (strstr(strtolower(join(':', $fields)), "link")) {
904                 echo "<b>",_("ADDING")," relation</b>"," ... ";
905                 $this->dbi->genericSqlQuery("ALTER TABLE $link_tbl ADD relation INT DEFAULT 0;");
906                 $this->dbi->genericSqlQuery("CREATE INDEX link_relation ON $link_tbl (relation);");
907             } else {
908                 echo _("FAIL");
909             }
910             echo "<br />\n";
911         }
912         if ($this->phpwiki_version >= 1030.12200610) {
913             echo _("Rebuild entire database to upgrade relation links")," ... ";
914             if (DATABASE_TYPE == 'dba') {
915                 echo "<b>",_("CONVERTING")," dba linktable</b>","(~2 min, max 4 min) ... ";
916                 flush();
917                 longer_timeout(240);
918                 $this->dbi->_backend->_linkdb->rebuild();
919             } else {
920                 flush();
921                 longer_timeout(180);
922                 $this->dbi->_backend->rebuild();
923             }
924             echo _("OK"), "<br />\n";
925         }
926     }
927
928     function CheckPluginUpdate() {
929         return;
930         
931         echo "<h3>",sprintf(_("check for necessary %s updates"),
932                             _("plugin argument")),"</h3>\n";
933                             
934         $this->_configUpdates = array();
935         $this->_configUpdates[] = new UpgradePluginEntry
936             ($this, array('key' => 'plugin_randompage_numpages', 
937                           'fixed_with' => 1012.0,
938                           //'header' => _("change RandomPage pages => numpages"),
939                           //'notice'  =>_("found RandomPage plugin"),
940                           'check_args' => array("plugin RandomPage pages",
941                                                 "/(<\?\s*plugin\s+ RandomPage\s+)pages/",
942                                                 "\\1numpages")));
943         $this->_configUpdates[] = new UpgradePluginEntry
944             ($this, array('key' => 'plugin_createtoc_position', 
945                           'fixed_with' => 1013.0,
946                           //'header' => _("change CreateToc align => position"),
947                           //'notice'  =>_("found CreateToc plugin"),
948                           'check_args' => array("plugin CreateToc align",
949                                                 "/(<\?\s*plugin\s+ CreateToc[^\?]+)align/",
950                                                 "\\1position")));
951
952         if (empty($this->_configUpdates)) return;
953         foreach ($this->_configUpdates as $update) {
954             $pages = $this->dbi->fullSearch($this->check_args[0]);
955             while ($page = $allpages->next()) {
956                 $current = $page->getCurrentRevision();
957                 $pagetext = $current->getPackedContent();
958                 $update->check($this->check_args[1], $this->check_args[2], $pagetext, $page, $current);
959             }
960         }
961         free($allpages);
962         unset($pagetext);
963         unset($current);
964         unset($page);
965     }
966
967     /**
968      * preg_replace over local file.
969      * Only line-orientated matches possible.
970      */
971     function fixLocalFile($match, $replace, $filename) {
972         $o_filename = $filename;
973         if (!file_exists($filename))
974             $filename = FindFile($filename);
975         if (!file_exists($filename))
976             return array(false, sprintf(_("file %s not found"), $o_filename));
977         $found = false;
978         if (is_writable($filename)) {
979             $in = fopen($filename, "rb");
980             $out = fopen($tmp = tempnam(getUploadFilePath(),"cfg"), "wb");
981             if (isWindows())
982                 $tmp = str_replace("/","\\",$tmp);
983             // Detect the existing linesep at first line. fgets strips it even if 'rb'.
984             // Before we simply assumed \r\n on windows local files.
985             $s = fread($in, 1024);
986             rewind($in);
987             $linesep = (substr_count($s, "\r\n") > substr_count($s, "\n")) ? "\r\n" : "\n";
988             //$linesep = isWindows() ? "\r\n" : "\n";
989             while ($s = fgets($in)) {
990                 // =>php-5.0.1 can fill count
991                 //$new = preg_replace($match, $replace, $s, -1, $count);
992                 $new = preg_replace($match, $replace, $s);
993                 if ($new != $s) {
994                     $s = $new . $linesep;
995                     $found = true;
996                 }
997                 fputs($out, $s);
998             }
999             fclose($in);
1000             fclose($out);
1001             if (!$found) {
1002                 // todo: skip
1003                 $reason = sprintf(_("%s not found in %s"), $match, $filename);
1004                 unlink($out);
1005                 return array($found, $reason);
1006             } else {
1007                 @unlink("$file.bak");
1008                 @rename($file,"$file.bak");
1009                 if (!rename($tmp, $file))
1010                     return array(false, sprintf(_("couldn't move %s to %s"), $tmp, $filename));
1011                 return true;    
1012             }
1013         } else {
1014             return array(false, sprintf(_("file %s is not writable"), $filename));
1015         }
1016     }
1017
1018     function CheckConfigUpdate () {
1019         echo "<h3>",sprintf(_("check for necessary %s updates"),
1020                             "config.ini"),"</h3>\n";
1021         $entry = new UpgradeConfigEntry
1022             ($this, array('key' => 'cache_control_none', 
1023                           'fixed_with' => 1012.0,
1024                           'header' => sprintf(_("check for %s"),"CACHE_CONTROL = NONE"),
1025                           'applicable_args' => 'CACHE_CONTROL',
1026                           'notice'  => _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"),
1027                           'check_args' => array("/^\s*CACHE_CONTROL\s*=\s*NONE/", "CACHE_CONTROL = NO_CACHE")));
1028         $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1029         $this->_configUpdates[] = $entry;
1030         
1031         $entry = new UpgradeConfigEntry
1032             ($this, array('key' => 'group_method_none', 
1033                           'fixed_with' => 1012.0,
1034                           'header' => sprintf(_("check for %s"), "GROUP_METHOD = NONE"),
1035                           'applicable_args' => 'GROUP_METHOD',
1036                           'notice'  =>_("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""),
1037                           'check_args' => array("/^\s*GROUP_METHOD\s*=\s*NONE/", "GROUP_METHOD = \"NONE\"")));
1038         $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1039         $this->_configUpdates[] = $entry;
1040
1041         $entry = new UpgradeConfigEntry
1042             ($this, array('key' => 'blog_empty_default_prefix', 
1043                           'fixed_with' => 1013.0,
1044                           'header' => sprintf(_("check for %s"), "BLOG_EMPTY_DEFAULT_PREFIX"),
1045                           'applicable_args' => 'BLOG_EMPTY_DEFAULT_PREFIX',
1046                           'notice'  =>_("fix BLOG_EMPTY_DEFAULT_PREFIX into BLOG_DEFAULT_EMPTY_PREFIX"),
1047                           'check_args' => array("/BLOG_EMPTY_DEFAULT_PREFIX\s*=/","BLOG_DEFAULT_EMPTY_PREFIX =")));
1048         $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined'));
1049         $this->_configUpdates[] = $entry;
1050
1051         // TODO: find extra file updates
1052         if (empty($this->_configUpdates)) return;
1053         foreach ($this->_configUpdates as $update) {
1054             $update->check();
1055         }
1056     }
1057
1058 } // class Upgrade
1059
1060 class UpgradeEntry 
1061 {
1062     /**
1063      * Add an upgrade item to be checked.
1064      *
1065      * @param $parent object The parent Upgrade class to inherit the version properties
1066      * @param $key string    A short unique key to store success in the WikiDB
1067      * @param $fixed_with double @see phpwiki_version() number
1068      * @param $header string Optional header to be printed always even if not applicable
1069      * @param $applicable WikiCallback Optional callback boolean applicable()
1070      * @param $notice string Description of the check
1071      * @param $method WikiCallback Optional callback array method(array)
1072      * //param All other args are passed to $method
1073      */
1074     function UpgradeEntry(&$parent, $params) {
1075         $this->parent =& $parent;           // get the properties db_version
1076         foreach (array('key' => 'required',
1077                         // the wikidb stores the version when we actually fixed that.
1078                        'fixed_with' => 'required',
1079                        'header' => '',          // always printed
1080                        'applicable_cb' => null, // method to check if applicable 
1081                        'applicable_args' => array(), // might be the config name
1082                        'notice' => '', 
1083                        'check_cb' => null,      // method to apply
1084                        'check_args' => array()) 
1085                  as $k => $v)
1086         {
1087             if (!isset($params[$k])) { // default
1088                 if ($v == 'required') trigger_error("Required arg $k missing", E_USER_ERROR);
1089                 else $this->{$k} = $v;
1090             } else { 
1091                 $this->{$k} = $params[$k];
1092             }
1093         }
1094         if (!is_array($this->applicable_args)) // single arg convenience shortcut
1095             $this->applicable_args = array($this->applicable_args);
1096         if (!is_array($this->check_args))   // single arg convenience shortcut
1097             $this->check_args = array($this->check_args);
1098         if ($this->notice === '' and count($this->applicable_args) > 0)
1099             $this->notice = 'Check for '.join(', ', $this->applicable_args);
1100         $this->_db_key = "_upgrade";
1101         $this->upgrade = $this->parent->dbi->get($this->_db_key);
1102     }
1103     /* needed ? */
1104     function setApplicableCb($object) {
1105         $this->applicable_cb =& $object; 
1106     }
1107     function _check_if_already_fixed() {
1108         // not yet fixed?
1109         if (!isset($this->upgrade['name'])) return false;
1110         // override with force?
1111         if ($this->parent->request->getArg('force')) return false;
1112         // already fixed and with an ok version
1113         if ($this->upgrade['name'] >= $this->fixed_with) return $this->upgrade['name'];
1114         // already fixed but with an older version. do it again.
1115         return false;
1116     }
1117     function pass() {
1118         // store in db no to fix again
1119         $this->upgrade['name'] = $this->parent->phpwiki_version;
1120         $this->parent->dbi->set($this->_db_key, $this->upgrade);
1121         echo "<b>",_("FIXED"),"</b>";
1122         if (isset($this->reason))
1123             echo ": ", $this->reason;
1124         echo "<br />\n";
1125         flush();
1126         return true;
1127     }
1128     function fail() {
1129         echo " <b><font color=\"red\">", _("FAILED"), "</font></b>";
1130         if (isset($this->reason))
1131             echo ": ", $this->reason;
1132         echo "<br />\n";
1133         flush();
1134         return false;
1135     }
1136     function skip() { // not applicable
1137         if (isset($this->silent_skip)) return true;
1138         echo _(" skipped"),".<br />\n";
1139         flush();
1140         return true;
1141     }
1142     function check($args = null) {
1143         if ($this->header) echo $this->header, ' ... ';
1144         if ($when = $this->_check_if_already_fixed()) {
1145             // be totally silent if no header is defined.
1146             if ($this->header) echo _("fixed with")," ",$when,"<br />\n";
1147             flush();
1148             return true;
1149         }
1150         if (is_object($this->applicable_cb)) {
1151             if (!$this->applicable_cb->call_array($this->applicable_args))
1152                 return $this->skip();
1153         }
1154         if ($this->notice) {
1155             if ($this->header)
1156                 echo "<br />\n";
1157             echo $this->notice," ";
1158             flush();
1159         }
1160         if (!is_null($args)) $this->check_args =& $args;
1161         if (is_object($this->check_cb))
1162             $do = $this->method_cb->call_array($this->check_args);
1163         else
1164             $do = $this->default_method($this->check_args);
1165         if (is_array($do)) {
1166             $this->reason = $do[1];
1167             $do = $do[0];
1168         }
1169         return $do ? $this->pass() : $this->fail();
1170     }
1171 } // class UpgradeEntry
1172
1173 class UpgradeConfigEntry extends UpgradeEntry {
1174     function _applicable_defined() {
1175         return (boolean)defined($this->applicable_args[0]);
1176     }
1177     function _applicable_defined_and_empty() {
1178         $const = $this->applicable_args[0];
1179         return (boolean)(defined($const) and !constant($const));
1180     }
1181     function default_method ($args) {
1182         $match = $args[0];
1183         $replace = $args[1];
1184         return $this->parent->fixLocalFile($match, $replace, "config/config.ini");
1185     }
1186 } // class UpdateConfigEntry
1187
1188 /* This is different */
1189 class UpgradePluginEntry extends UpgradeEntry {
1190     
1191    /**
1192      * check all pages for a plugin match
1193      */
1194     var $silent_skip = 1;
1195      
1196     function default_method (&$args) {
1197         $match    =  $args[0];
1198         $replace  =  $args[1];
1199         $pagetext =& $args[2];
1200         $page     =& $args[3];
1201         $current  =& $args[4];
1202         if (preg_match($match, $pagetext)) {
1203             echo $page->getName()," ",$this->notice," ... ";
1204             if ($newtext = preg_replace($match, $replace, $pagetext)) {
1205                 $meta = $current->_data;
1206                 $meta['summary'] = "upgrade: ".$this->header;
1207                 $page->save($newtext, $current->getVersion() + 1, $meta);
1208                 $this->pass();
1209             } else {
1210                 $this->fail();
1211             }
1212         }
1213     }
1214 } // class UpdatePluginEntry
1215
1216 /**
1217  * fix custom themes which are not in our distribution
1218  * this should be optional
1219  */
1220 class UpgradeThemeEntry extends UpgradeEntry {
1221
1222     function default_method (&$args) {
1223         $match    =  $args[0];
1224         $replace  =  $args[1];
1225         $template = $args[2];
1226     }
1227     
1228     function fixThemeTemplate($match, $new, $template) {
1229         // for all custom themes
1230         $ourthemes = explode(":","blog:Crao:default:Hawaiian:MacOSX:MonoBook:Portland:shamino_com:SpaceWiki:wikilens:Wordpress");
1231         $themedir = NormalizeLocalFileName("themes");
1232         $dh = opendir($themedir);
1233         while ($r = readdir($dh)) {
1234             if (filetype($r) == 'dir' and $r[0] != '.' and !is_array($r, $ourthemes))
1235                 $customthemes[] = $r;
1236         }
1237         $success = true;
1238         $errors = '';
1239         foreach ($customthemes as $customtheme) {
1240             $template = FindFile("themes/$customtheme/templates/$template");
1241             $do = $this->parent->fixLocalFile($match, $new, template);
1242             if (!$do[0]) {
1243                 $success = false;
1244                 $errors .= $do[1]." "; 
1245                 echo $do[1];
1246             }
1247         }
1248         return array($success, $errors);
1249     }
1250 }
1251
1252 /**
1253  * TODO:
1254  *
1255  * Upgrade: Base class for multipage worksteps
1256  * identify, validate, display options, next step
1257  */
1258 /*
1259 */
1260
1261 // TODO: At which step are we? 
1262 // validate and do it again or go on with next step.
1263
1264 /** entry function from lib/main.php
1265  */
1266 function DoUpgrade(&$request) {
1267
1268     if (!$request->_user->isAdmin()) {
1269         $request->_notAuthorized(WIKIAUTH_ADMIN);
1270         $request->finish(
1271                          HTML::div(array('class' => 'disabled-plugin'),
1272                                    fmt("Upgrade disabled: user != isAdmin")));
1273         return;
1274     }
1275     // TODO: StartLoadDump should turn on implicit_flush.   
1276     @ini_set("implicit_flush", true);
1277     StartLoadDump($request, _("Upgrading this PhpWiki"));
1278     $upgrade = new Upgrade($request);
1279     //if (!$request->getArg('noindex'))
1280     //    CheckOldIndexUpdate($request); // index.php => config.ini to upgrade from < 1.3.10
1281     if (!$request->getArg('nodb'))
1282         $upgrade->CheckDatabaseUpdate($request);   // first check cached_html and friends
1283     if (!$request->getArg('nopgsrc')) {
1284         $upgrade->CheckActionPageUpdate($request);
1285         $upgrade->CheckPgsrcUpdate($request);
1286     }
1287     if (!$request->getArg('noplugin'))
1288         $upgrade->CheckPluginUpdate($request);
1289     if (!$request->getArg('noconfig'))
1290         $upgrade->CheckConfigUpdate($request);
1291     // This is optional and should be linked. In EndLoadDump or PhpWikiAdministration?
1292     //if ($request->getArg('theme'))
1293     //    $upgrade->CheckThemeUpdate($request);
1294     EndLoadDump($request);
1295 }
1296
1297
1298 /*
1299  $Log: not supported by cvs2svn $
1300  Revision 1.60  2007/08/25 18:43:35  rurban
1301  add missing DBParams. Detected by Sacha Schär.
1302
1303  Revision 1.59  2007/06/09 18:57:44  rurban
1304  fix action=upgrade for dba
1305
1306  Revision 1.58  2007/01/28 22:54:18  rurban
1307  more objectification. store last db update.
1308
1309  Revision 1.57  2007/01/04 16:43:09  rurban
1310  Changed to class Upgrade: Do not pollute our namespace with global functions. Less arguments needed. Fix missing permissions on wrong DBADMIN_USER. Let user input override the wrong constant.
1311
1312  Revision 1.56  2007/01/03 21:25:34  rurban
1313  rename InterWikiKarte to InterWikiListe. Support nosql, nopgsrc, noplugin, noconfig args.
1314
1315  Revision 1.55  2007/01/02 13:24:01  rurban
1316  1.3.13 support: _rename_page_helper, _rename_to_help_page, _upgrade_relation_links, check for ACCESS_LOG_SQL remote_host varchar(50), _upgrade_psql_tsearch2
1317
1318  Revision 1.54  2006/12/03 17:07:29  rurban
1319  #1535843 by matt brown: Upgrade Wizard Password fixes are not portable
1320
1321  Revision 1.53  2006/12/03 17:03:18  rurban
1322  #1535851 by matt brown
1323
1324  Revision 1.52  2006/12/03 17:01:18  rurban
1325  #1535839 by matt brown
1326
1327  Revision 1.51  2006/08/07 21:05:30  rurban
1328  patch #1535837  (debian)
1329
1330  Revision 1.50  2006/06/18 11:04:09  rurban
1331  keep overwrite arg
1332
1333  Revision 1.49  2006/05/18 06:03:39  rurban
1334  use $dbh->_backend->isSQL
1335
1336  Revision 1.48  2005/11/14 22:32:38  rurban
1337  remove user, SKIP on !session
1338
1339  Revision 1.47  2005/02/27 19:13:27  rurban
1340  latin1 mysql fix
1341
1342  Revision 1.46  2005/02/12 17:22:18  rurban
1343  locale update: missing . : fixed. unified strings
1344  proper linebreaks
1345
1346  Revision 1.45  2005/02/10 19:01:19  rurban
1347  add PDO support
1348
1349  Revision 1.44  2005/02/07 15:40:42  rurban
1350  use defined CHARSET for db. more comment
1351  Revision 1.43  2005/02/04 11:44:07  rurban
1352  check passwd in access_log
1353
1354  Revision 1.42  2005/02/02 19:38:13  rurban
1355  prefer utf8 pagenames for collate issues
1356
1357  Revision 1.41  2005/01/31 12:15:29  rurban
1358  print OK
1359
1360  Revision 1.40  2005/01/30 23:22:17  rurban
1361  clarify messages
1362
1363  Revision 1.39  2005/01/30 23:09:17  rurban
1364  sanify session fields
1365
1366  Revision 1.38  2005/01/25 07:57:02  rurban
1367  add dbadmin form, add mysql LOCK TABLES check, add plugin args updater (not yet activated)
1368
1369  Revision 1.37  2005/01/20 10:19:08  rurban
1370  add InterWikiMap to special pages
1371
1372  Revision 1.36  2004/12/20 12:56:11  rurban
1373  patch #1088128 by Kai Krakow. avoid chicken & egg problem
1374
1375  Revision 1.35  2004/12/13 14:35:41  rurban
1376  verbose arg
1377
1378  Revision 1.34  2004/12/11 09:39:28  rurban
1379  needed init for ref
1380
1381  Revision 1.33  2004/12/10 22:33:39  rurban
1382  add WikiAdminUtils method for convert-cached-html
1383  missed some vars.
1384
1385  Revision 1.32  2004/12/10 22:15:00  rurban
1386  fix $page->get('_cached_html)
1387  refactor upgrade db helper _convert_cached_html() to be able to call them from WikiAdminUtils also.
1388  support 2nd genericSqlQuery param (bind huge arg)
1389
1390  Revision 1.31  2004/12/10 02:45:26  rurban
1391  SQL optimization:
1392    put _cached_html from pagedata into a new seperate blob, not huge serialized string.
1393    it is only rarelely needed: for current page only, if-not-modified
1394    but was extracted for every simple page iteration.
1395
1396  Revision 1.30  2004/11/29 17:58:57  rurban
1397  just aesthetics
1398
1399  Revision 1.29  2004/11/29 16:08:31  rurban
1400  added missing nl
1401
1402  Revision 1.28  2004/11/16 16:25:14  rurban
1403  fix accesslog tablename, print CREATED only if really done
1404
1405  Revision 1.27  2004/11/07 16:02:52  rurban
1406  new sql access log (for spam prevention), and restructured access log class
1407  dbh->quote (generic)
1408  pear_db: mysql specific parts seperated (using replace)
1409
1410  Revision 1.26  2004/10/14 19:19:34  rurban
1411  loadsave: check if the dumped file will be accessible from outside.
1412  and some other minor fixes. (cvsclient native not yet ready)
1413
1414  Revision 1.25  2004/09/06 08:28:00  rurban
1415  rename genericQuery to genericSqlQuery
1416
1417  Revision 1.24  2004/07/05 13:56:22  rurban
1418  sqlite autoincrement fix
1419
1420  Revision 1.23  2004/07/04 10:28:06  rurban
1421  DBADMIN_USER fix
1422
1423  Revision 1.22  2004/07/03 17:21:28  rurban
1424  updated docs: submitted new mysql bugreport (#1491 did not fix it)
1425
1426  Revision 1.21  2004/07/03 16:51:05  rurban
1427  optional DBADMIN_USER:DBADMIN_PASSWD for action=upgrade (if no ALTER permission)
1428  added atomic mysql REPLACE for PearDB as in ADODB
1429  fixed _lock_tables typo links => link
1430  fixes unserialize ADODB bug in line 180
1431
1432  Revision 1.20  2004/07/03 14:48:18  rurban
1433  Tested new mysql 4.1.3-beta: binary search bug as fixed.
1434  => fixed action=upgrade,
1435  => version check in PearDB also (as in ADODB)
1436
1437  Revision 1.19  2004/06/19 12:19:09  rurban
1438  slightly improved docs
1439
1440  Revision 1.18  2004/06/19 11:47:17  rurban
1441  added CheckConfigUpdate: CACHE_CONTROL = NONE => NO_CACHE
1442
1443  Revision 1.17  2004/06/17 11:31:50  rurban
1444  check necessary localized actionpages
1445
1446  Revision 1.16  2004/06/16 10:38:58  rurban
1447  Disallow refernces in calls if the declaration is a reference
1448  ("allow_call_time_pass_reference clean").
1449    PhpWiki is now allow_call_time_pass_reference = Off clean,
1450    but several external libraries may not.
1451    In detail these libs look to be affected (not tested):
1452    * Pear_DB odbc
1453    * adodb oracle
1454
1455  Revision 1.15  2004/06/07 19:50:40  rurban
1456  add owner field to mimified dump
1457
1458  Revision 1.14  2004/06/07 18:38:18  rurban
1459  added mysql 4.1.x search fix
1460
1461  Revision 1.13  2004/06/04 20:32:53  rurban
1462  Several locale related improvements suggested by Pierrick Meignen
1463  LDAP fix by John Cole
1464  reanable admin check without ENABLE_PAGEPERM in the admin plugins
1465
1466  Revision 1.12  2004/05/18 13:59:15  rurban
1467  rename simpleQuery to genericSqlQuery
1468
1469  Revision 1.11  2004/05/15 13:06:17  rurban
1470  skip the HomePage, at first upgrade the ActionPages, then the database, then the rest
1471
1472  Revision 1.10  2004/05/15 01:19:41  rurban
1473  upgrade prefix fix by Kai Krakow
1474
1475  Revision 1.9  2004/05/14 11:33:03  rurban
1476  version updated to 1.3.11pre
1477  upgrade stability fix
1478
1479  Revision 1.8  2004/05/12 10:49:55  rurban
1480  require_once fix for those libs which are loaded before FileFinder and
1481    its automatic include_path fix, and where require_once doesn't grok
1482    dirname(__FILE__) != './lib'
1483  upgrade fix with PearDB
1484  navbar.tmpl: remove spaces for IE &nbsp; button alignment
1485
1486  Revision 1.7  2004/05/06 17:30:38  rurban
1487  CategoryGroup: oops, dos2unix eol
1488  improved phpwiki_version:
1489    pre -= .0001 (1.3.10pre: 1030.099)
1490    -p1 += .001 (1.3.9-p1: 1030.091)
1491  improved InstallTable for mysql and generic SQL versions and all newer tables so far.
1492  abstracted more ADODB/PearDB methods for action=upgrade stuff:
1493    backend->backendType(), backend->database(),
1494    backend->listOfFields(),
1495    backend->listOfTables(),
1496
1497  Revision 1.6  2004/05/03 15:05:36  rurban
1498  + table messages
1499
1500  Revision 1.4  2004/05/02 21:26:38  rurban
1501  limit user session data (HomePageHandle and auth_dbi have to invalidated anyway)
1502    because they will not survive db sessions, if too large.
1503  extended action=upgrade
1504  some WikiTranslation button work
1505  revert WIKIAUTH_UNOBTAINABLE (need it for main.php)
1506  some temp. session debug statements
1507
1508  Revision 1.3  2004/04/29 22:33:30  rurban
1509  fixed sf.net bug #943366 (Kai Krakow)
1510    couldn't load localized url-undecoded pagenames
1511
1512  Revision 1.2  2004/03/12 15:48:07  rurban
1513  fixed explodePageList: wrong sortby argument order in UnfoldSubpages
1514  simplified lib/stdlib.php:explodePageList
1515
1516  */
1517
1518 // For emacs users
1519 // Local Variables:
1520 // mode: php
1521 // tab-width: 8
1522 // c-basic-offset: 4
1523 // c-hanging-comment-ender-p: nil
1524 // indent-tabs-mode: nil
1525 // End:
1526 ?>