]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/upgrade.php
rcs_id no longer makes sense with Subversion global version number
[SourceForge/phpwiki.git] / lib / upgrade.php
1 <?php //-*-php-*-
2 // rcs_id('$Id$');
3 /*
4  * Copyright 2004,2005,2006,2007 $ThePhpWikiProgrammingTeam
5  * Copyright 2008 Marc-Etienne Vargenau, Alcatel-Lucent
6  *
7  * This file is part of PhpWiki.
8  *
9  * PhpWiki is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * PhpWiki is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with PhpWiki; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23
24 /**
25  * Upgrade existing WikiDB and config settings after installing a new PhpWiki sofwtare version.
26  * Status: almost no queries for verification. 
27  *         simple merge conflict resolution, or Overwrite All.
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. (for newer changes since 1.3.11, not yet)
40  *   3a. Convert old-style index.php into config/config.ini. (easy, not yet)
41  *  4. Check for changed plugin invocation arguments. (medium, done)
42  *  5. Check for changed theme variables. (hard, not yet)
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 class Upgrade {
53
54     function Upgrade (&$request) {
55         $this->request =& $request;
56         $this->dbi =& $request->_dbi; // no reference for dbadmin ? 
57         $this->phpwiki_version = $this->current_db_version = phpwiki_version();
58         //$this->current_db_version = 1030.13; // should be stored in the db. should be phpwiki_version
59
60         $this->db_version = $this->dbi->get_db_version();
61         $this->isSQL = $this->dbi->_backend->isSQL();
62     }
63
64     /**
65      * TODO: check for the pgsrc_version number, not the revision mtime only
66      */
67     function doPgsrcUpdate($pagename, $path, $filename) {
68         // don't ever update the HomePage
69         if ((defined(HOME_PAGE) and ($pagename == HOME_PAGE))
70             or ($pagename == _("HomePage"))
71             or ($pagename == "HomePage")) {
72             echo "$path/$pagename: ",_("always skip the HomePage."),
73             _(" skipped"),".<br />\n";
74             return;
75         }
76
77         $page = $this->dbi->getPage($pagename);
78         if ($page->exists()) {
79             // check mtime: update automatically if pgsrc is newer
80             $rev = $page->getCurrentRevision();
81             $page_mtime = $rev->get('mtime');
82             $data  = implode("", file($path."/".$filename));
83             if (($parts = ParseMimeifiedPages($data))) {
84                 usort($parts, 'SortByPageVersion');
85                 reset($parts);
86                 $pageinfo = $parts[0];
87                 $stat  = stat($path."/".$filename);
88                 $new_mtime = 0;
89                 if (isset($pageinfo['versiondata']['mtime']))
90                     $new_mtime = $pageinfo['versiondata']['mtime'];
91                 if (!$new_mtime and isset($pageinfo['versiondata']['lastmodified']))
92                     $new_mtime = $pageinfo['versiondata']['lastmodified'];
93                 if (!$new_mtime and isset($pageinfo['pagedata']['date']))
94                     $new_mtime = $pageinfo['pagedata']['date'];
95                 if (!$new_mtime)
96                     $new_mtime = $stat[9];
97                 if ($new_mtime > $page_mtime) {
98                     echo "$path/$pagename: ",_("newer than the existing page."),
99                         _(" replace "),"($new_mtime &gt; $page_mtime)","<br />\n";
100                     LoadAny($this->request, $path."/".$filename);
101                     echo "<br />\n";
102                 } else {
103                     echo "$path/$pagename: ",_("older than the existing page."),
104                         _(" skipped"),".<br />\n";
105                 }
106             } else {
107                 echo "$path/$pagename: ",("unknown format."),
108                     _(" skipped"),".<br />\n";
109             }
110         } else {
111             echo sprintf(_("%s does not exist"),$pagename),"<br />\n";
112             LoadAny($this->request, $path."/".$filename);
113             echo "<br />\n";
114         }
115     }
116
117     function CheckActionPageUpdate() {
118         echo "<h3>",sprintf(_("check for necessary %s updates"),
119                             _("ActionPage")),"</h3>\n";
120         // 1.3.13 before we pull in all missing pages, we rename existing ones
121         $this->_rename_page_helper(_("_AuthInfo"), _("DebugAuthInfo"));
122         // this is in some templates. so we keep the old name
123         //$this->_rename_page_helper($this->dbi, _("DebugInfo"), _("DebugBackendInfo")); 
124         $this->_rename_page_helper(_("_GroupInfo"), _("GroupAuthInfo")); //never officially existed
125         $this->_rename_page_helper("InterWikiKarte", "InterWikiListe"); // german only
126  
127         $path = FindFile('pgsrc');
128         $pgsrc = new fileSet($path);
129         // most actionpages have the same name as the plugin
130         $loc_path = FindLocalizedFile('pgsrc');
131         foreach ($pgsrc->getFiles() as $filename) {
132             if (substr($filename,-1,1) == '~') continue;
133             if (substr($filename,-5,5) == '.orig') continue;
134             $pagename = urldecode($filename);
135             if (isActionPage($filename)) {
136                 $translation = gettext($pagename);
137                 if ($translation == $pagename)
138                     $this->doPgsrcUpdate($pagename, $path, $filename);
139                 elseif (FindLocalizedFile('pgsrc/'.urlencode($translation),1))
140                     $this->doPgsrcUpdate($translation, $loc_path, urlencode($translation));
141                 else
142                     $this->doPgsrcUpdate($pagename, $path, $filename);
143             }
144         }
145     }
146
147     // see loadsave.php for saving new pages.
148     function CheckPgsrcUpdate() {
149         echo "<h3>",sprintf(_("check for necessary %s updates"),
150                             "pgsrc"),"</h3>\n";
151         if ($this->db_version < 1030.12200612) {
152             echo "<h4>",_("rename to Help: pages"),"</h4>\n";
153         }
154         $path = FindLocalizedFile(WIKI_PGSRC);
155         $pgsrc = new fileSet($path);
156         // fixme: verification, ...
157         foreach ($pgsrc->getFiles() as $filename) {
158             if (substr($filename,-1,1) == '~') continue;
159             if (substr($filename,-5,5) == '.orig') continue;
160             $pagename = urldecode($filename);
161             if (!isActionPage($filename)) {
162                 // There're a lot of now unneeded pages around. 
163                 // At first rename the BlaPlugin pages to Help/<pagename> and then to the update.
164                 if ($this->db_version < 1030.12200612) {
165                     $this->_rename_to_help_page($pagename);    
166                 }
167                 $this->doPgsrcUpdate($pagename,$path,$filename);
168             }
169         }
170
171         // Now check some theme specific pgsrc files (blog, wikilens, custom). 
172         global $WikiTheme;
173         $path = $WikiTheme->file("pgsrc");
174         // TBD: the call to fileSet prints a warning:
175         // Notice: Unable to open directory 'themes/MonoBook/pgsrc' for reading
176         $pgsrc = new fileSet($path);
177         if ($pgsrc->getFiles()) {
178             echo "<h3>",sprintf(_("check for additional theme %s updates"),
179                                 "pgsrc"),"</h3>\n";
180             foreach ($pgsrc->getFiles() as $filename) {
181                 if (substr($filename,-1,1) == '~') continue;
182                 if (substr($filename,-5,5) == '.orig') continue;
183                 $pagename = urldecode($filename);
184                 $this->doPgsrcUpdate($pagename,$path,$filename);
185             }
186         }
187         return;
188     }
189
190     function _rename_page_helper($oldname, $pagename) {
191         echo sprintf(_("rename %s to %s"), $oldname, $pagename)," ...";
192         if ($this->dbi->isWikiPage($oldname) and !$this->dbi->isWikiPage($pagename)) {
193             if ($this->dbi->_backend->rename_page($oldname, $pagename))
194                 echo _("OK")," <br />\n";
195             else
196                 echo " <b><font color=\"red\">", _("FAILED"), "</font></b>",
197                     " <br />\n";
198         } else {
199             echo _(" skipped")," <br />\n";
200         }
201     }
202
203     function _rename_to_help_page($pagename) {
204         $newprefix = _("Help") . "/";
205         if (substr($pagename,0,strlen($newprefix)) != $newprefix) return;       
206         $oldname = substr($pagename,strlen($newprefix));
207         $this->_rename_page_helper($oldname, $pagename);
208     }
209
210     /**
211      * TODO: Search table definition in appropriate schema
212      *       and create it.
213      * Supported: mysql and generic SQL, for ADODB and PearDB.
214      */
215     function installTable($table, $backend_type) {
216         global $DBParams;
217         if (!$this->isSQL) return;
218         echo _("MISSING")," ... \n";
219         $backend = &$this->dbi->_backend->_dbh;
220         /*
221           $schema = findFile("schemas/${backend_type}.sql");
222           if (!$schema) {
223           echo "  ",_("FAILED"),": ",sprintf(_("no schema %s found"),
224           "schemas/${backend_type}.sql")," ... <br />\n";
225           return false;
226           }
227         */
228         extract($this->dbi->_backend->_table_names);
229         $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
230         switch ($table) {
231         case 'session':
232             assert($session_tbl);
233             if ($backend_type == 'mysql') {
234                 $this->dbi->genericSqlQuery("
235 CREATE TABLE $session_tbl (
236         sess_id         CHAR(32) NOT NULL DEFAULT '',
237         sess_data       BLOB NOT NULL,
238         sess_date       INT UNSIGNED NOT NULL,
239         sess_ip         CHAR(15) NOT NULL,
240         PRIMARY KEY (sess_id),
241         INDEX (sess_date)
242 )");
243             } else {
244                 $this->dbi->genericSqlQuery("
245 CREATE TABLE $session_tbl (
246         sess_id         CHAR(32) NOT NULL DEFAULT '',
247         sess_data       ".($backend_type == 'pgsql'?'TEXT':'BLOB')." NOT NULL,
248         sess_date       INT,
249         sess_ip         CHAR(15) NOT NULL
250 )");
251                 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
252             }
253             $this->dbi->genericSqlQuery("CREATE INDEX sess_date on session (sess_date)");
254             echo "  ",_("CREATED");
255             break;
256         case 'pref':
257             $pref_tbl = $prefix.'pref';
258             if ($backend_type == 'mysql') {
259                 $this->dbi->genericSqlQuery("
260 CREATE TABLE $pref_tbl (
261         userid  CHAR(48) BINARY NOT NULL UNIQUE,
262         prefs   TEXT NULL DEFAULT '',
263         PRIMARY KEY (userid)
264 )");
265             } else {
266                 $this->dbi->genericSqlQuery("
267 CREATE TABLE $pref_tbl (
268         userid  CHAR(48) NOT NULL,
269         prefs   TEXT NULL DEFAULT ''
270 )");
271                 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
272             }
273             echo "  ",_("CREATED");
274             break;
275         case 'member':
276             $member_tbl = $prefix.'member';
277             if ($backend_type == 'mysql') {
278                 $this->dbi->genericSqlQuery("
279 CREATE TABLE $member_tbl (
280         userid    CHAR(48) BINARY NOT NULL,
281         groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
282         INDEX (userid),
283         INDEX (groupname)
284 )");
285             } else {
286                 $this->dbi->genericSqlQuery("
287 CREATE TABLE $member_tbl (
288         userid    CHAR(48) NOT NULL,
289         groupname CHAR(48) NOT NULL DEFAULT 'users'
290 )");
291                 $this->dbi->genericSqlQuery("CREATE INDEX userid ON $member_tbl (userid)");
292                 $this->dbi->genericSqlQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
293             }
294             echo "  ",_("CREATED");
295             break;
296         case 'rating':
297             $rating_tbl = $prefix.'rating';
298             if ($backend_type == 'mysql') {
299                 $this->dbi->genericSqlQuery("
300 CREATE TABLE $rating_tbl (
301         dimension INT(4) NOT NULL,
302         raterpage INT(11) NOT NULL,
303         rateepage INT(11) NOT NULL,
304         ratingvalue FLOAT NOT NULL,
305         rateeversion INT(11) NOT NULL,
306         tstamp TIMESTAMP(14) NOT NULL,
307         PRIMARY KEY (dimension, raterpage, rateepage)
308 )");
309             } else {
310                 $this->dbi->genericSqlQuery("
311 CREATE TABLE $rating_tbl (
312         dimension INT(4) NOT NULL,
313         raterpage INT(11) NOT NULL,
314         rateepage INT(11) NOT NULL,
315         ratingvalue FLOAT NOT NULL,
316         rateeversion INT(11) NOT NULL,
317         tstamp TIMESTAMP(14) NOT NULL
318 )");
319                 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX rating"
320                                       ." ON $rating_tbl (dimension, raterpage, rateepage)");
321             }
322             echo "  ",_("CREATED");
323             break;
324         case 'accesslog':
325             $log_tbl = $prefix.'accesslog';
326             // fields according to http://www.outoforder.cc/projects/apache/mod_log_sql/docs-2.0/#id2756178
327             /*
328               A User Agent agent        varchar(255)    Mozilla/4.0 (compat; MSIE 6.0; Windows)
329               a CGi request arguments   request_args    varchar(255)    user=Smith&cart=1231&item=532
330               b Bytes transfered        bytes_sent      int unsigned    32561
331               c???      Text of cookie  cookie  varchar(255)    Apache=sdyn.fooonline.net 1300102700823
332               f Local filename requested        request_file    varchar(255)    /var/www/html/books-cycroad.html
333               H HTTP request_protocol   request_protocol        varchar(10)     HTTP/1.1
334               h Name of remote host     remote_host     varchar(50)     blah.foobar.com
335               I Request ID (from modd_unique_id)        id      char(19)        POlFcUBRH30AAALdBG8
336               l Ident user info remote_logname  varcgar(50)     bobby
337               M Machine ID???   machine_id      varchar(25)     web01
338               m HTTP request method     request_method  varchar(10)     GET
339               P httpd cchild PID        child_pid       smallint unsigned       3215
340               p http port       server_port     smallint unsigned       80
341               R Referer referer varchar(255)    http://www.biglinks4u.com/linkpage.html
342               r Request in full form    request_line    varchar(255)    GET /books-cycroad.html HTTP/1.1
343               S Time of request in UNIX time_t format   time_stamp      int unsigned    1005598029
344               T Seconds to service request      request_duration        smallint unsigned       2
345               t Time of request in human format request_time    char(28)        [02/Dec/2001:15:01:26 -0800]
346               U Request in simple form  request_uri     varchar(255)    /books-cycroad.html
347               u User info from HTTP auth        remote_user     varchar(50)     bobby
348               v Virtual host servicing the request      virtual_host    varchar(255)
349             */
350             $this->dbi->genericSqlQuery("
351 CREATE TABLE $log_tbl (
352         time_stamp    int unsigned,
353         remote_host   varchar(100),
354         remote_user   varchar(50),
355         request_method varchar(10),
356         request_line  varchar(255),
357         request_args  varchar(255),
358         request_uri   varchar(255),
359         request_time  char(28),
360         status        smallint unsigned,
361         bytes_sent    smallint unsigned,
362         referer       varchar(255), 
363         agent         varchar(255),
364         request_duration float
365 )");
366             $this->dbi->genericSqlQuery("CREATE INDEX log_time ON $log_tbl (time_stamp)");
367             $this->dbi->genericSqlQuery("CREATE INDEX log_host ON $log_tbl (remote_host)");
368             echo "  ",_("CREATED");
369             break;
370         }
371         echo "<br />\n";
372     }
373
374     /**
375      * Update from ~1.3.4 to current.
376      * tables: Only session, user, pref and member
377      * jeffs-hacks database api (around 1.3.2) later:
378      *   people should export/import their pages if using that old versions.
379      */
380     function CheckDatabaseUpdate() {
381         global $DBAuthParams, $DBParams;
382
383         echo "<h3>",sprintf(_("check for necessary %s updates"),
384                             _("database")),
385             " - ", DATABASE_TYPE,"</h3>\n";
386         $dbadmin = $this->request->getArg('dbadmin');
387         if ($this->isSQL) {
388             $this->_db_init();
389             if (isset($dbadmin['cancel'])) {
390                 echo _("CANCEL")," <br />\n";
391                 return;
392             }
393         }
394         echo "db version: we want ", $this->current_db_version, "\n<br />";
395         echo "db version: we have ", $this->db_version, "\n<br />";
396         if ($this->db_version >= $this->current_db_version) {
397             echo _("OK"), "<br />\n";
398             return;
399         }
400
401         $backend_type = $this->dbi->_backend->backendType();
402         if ($this->isSQL) {
403             echo "<h4>",_("Backend type: "),$backend_type,"</h4>\n";
404             $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
405             $tables = $this->dbi->_backend->listOfTables();
406             foreach (explode(':','session:pref:member') as $table) {
407                 echo sprintf(_("check for table %s"), $table)," ...";
408                 if (!in_array($prefix.$table, $tables)) {
409                     $this->installTable($table, $backend_type);
410                 } else {
411                     echo _("OK")," <br />\n";
412                 }
413             }
414         }
415
416         if ($this->phpwiki_version >= 1030.12200612 and $this->db_version < 1030.13) {
417             if ($this->isSQL and preg_match("/(pgsql|postgres)/", $backend_type)) {
418                 trigger_error("You need to upgrade to schema/psql-initialize.sql manually!", 
419                               E_USER_WARNING);
420                 // $this->_upgrade_psql_tsearch2();
421             }
422             $this->_upgrade_relation_links();
423         }
424
425         if (ACCESS_LOG_SQL and $this->isSQL) {
426             $table = "accesslog";
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         if ($this->isSQL and (class_exists("RatingsUserFactory") or $this->dbi->isWikiPage(_("RateIt")))) {
435             $table = "rating";
436             echo sprintf(_("check for table %s"), $table)," ...";
437             if (!in_array($prefix.$table, $tables)) {
438                 $this->installTable($table, $backend_type);
439             } else {
440                 echo _("OK")," <br />\n";
441             }
442         }
443         $backend = &$this->dbi->_backend->_dbh;
444         if ($this->isSQL)
445             extract($this->dbi->_backend->_table_names);
446
447         // 1.3.8 added session.sess_ip
448         if ($this->isSQL and $this->phpwiki_version >= 1030.08 and USE_DB_SESSION 
449             and isset($this->request->_dbsession)) 
450         {
451             echo _("check for new session.sess_ip column")," ... ";
452             $database = $this->dbi->_backend->database();
453             assert(!empty($DBParams['db_session_table']));
454             $session_tbl = $prefix . $DBParams['db_session_table'];
455             $sess_fields = $this->dbi->_backend->listOfFields($database, $session_tbl);
456             if (!$sess_fields) {
457                 echo _("SKIP");
458             } elseif (!strstr(strtolower(join(':', $sess_fields)), "sess_ip")) {
459                 // TODO: postgres test (should be able to add columns at the end, but not in between)
460                 echo "<b>",_("ADDING"),"</b>"," ... ";          
461                 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
462                 $this->dbi->genericSqlQuery("CREATE INDEX sess_date ON $session_tbl (sess_date)");
463             } else {
464                 echo _("OK");
465             }
466             echo "<br />\n";
467             if (substr($backend_type,0,5) == 'mysql') {
468                 // upgrade to 4.1.8 destroyed my session table: 
469                 // sess_id => varchar(10), sess_data => varchar(5). For others obviously also.
470                 echo _("check for mysql session.sess_id sanity")," ... ";
471                 $result = $this->dbi->genericSqlQuery("DESCRIBE $session_tbl");
472                 if (DATABASE_TYPE == 'SQL') {
473                     $iter = new WikiDB_backend_PearDB_generic_iter($backend, $result);
474                 } elseif (DATABASE_TYPE == 'ADODB') {
475                     $iter = new WikiDB_backend_ADODB_generic_iter($backend, $result, 
476                                                                   array("Field", "Type", "Null", "Key", "Default", "Extra"));
477                 } elseif (DATABASE_TYPE == 'PDO') {
478                     $iter = new WikiDB_backend_PDO_generic_iter($backend, $result);
479                 }
480                 while ($col = $iter->next()) {
481                     if ($col["Field"] == 'sess_id' and !strstr(strtolower($col["Type"]), 'char(32)')) {
482                         $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_id"
483                                               ." sess_id CHAR(32) NOT NULL");
484                         echo "sess_id ", $col["Type"], " ", _("fixed"), " =&gt; CHAR(32) ";
485                     }
486                     if ($col["Field"] == 'sess_ip' and !strstr(strtolower($col["Type"]), 'char(15)')) {
487                         $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_ip"
488                                               ." sess_ip CHAR(15) NOT NULL");
489                         echo "sess_ip ", $col["Type"], " ", _("fixed"), " =&gt; CHAR(15) ";
490                     }
491                 }
492                 echo _("OK"), "<br />\n";
493             }
494         }
495
496         /* TODO:
497            ALTER TABLE link ADD relation INT DEFAULT 0;
498            CREATE INDEX linkrelation ON link (relation);
499         */
500
501         // mysql >= 4.0.4 requires LOCK TABLE privileges
502         if (substr($backend_type,0,5) == 'mysql') {
503             echo _("check for mysql LOCK TABLE privilege")," ...";
504             $mysql_version = $this->dbi->_backend->_serverinfo['version'];
505             if ($mysql_version > 400.40) {
506                 if (!empty($this->dbi->_backend->_parsedDSN))
507                     $parseDSN = $this->dbi->_backend->_parsedDSN;
508                 elseif (function_exists('parseDSN')) // ADODB or PDO
509                     $parseDSN = parseDSN($DBParams['dsn']);
510                 else                         // pear
511                     $parseDSN = DB::parseDSN($DBParams['dsn']);
512                 $username = $this->dbi->_backend->qstr($parseDSN['username']);
513                 // on db level
514                 $query = "SELECT lock_tables_priv FROM mysql.db WHERE user='$username'";
515                 //mysql_select_db("mysql", $this->dbi->_backend->connection());
516                 $db_fields = $this->dbi->_backend->listOfFields("mysql", "db");
517                 if (!strstr(strtolower(join(':', $db_fields)), "lock_tables_priv")) {
518                     echo join(':', $db_fields);
519                     die("lock_tables_priv missing. The DB Admin must run mysql_fix_privilege_tables");
520                 }
521                 $row = $this->dbi->_backend->getRow($query);
522                 if (isset($row[0]) and $row[0] == 'N') {
523                     $this->dbi->genericSqlQuery("UPDATE mysql.db SET lock_tables_priv='Y'"
524                                           ." WHERE mysql.user='$username'");
525                     $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
526                     echo "mysql.db user='$username'", _("fixed"), "<br />\n";
527                 } elseif (!$row) {
528                     // or on user level
529                     $query = "SELECT lock_tables_priv FROM mysql.user WHERE user='$username'";
530                     $row = $this->dbi->_backend->getRow($query);
531                     if ($row and $row[0] == 'N') {
532                         $this->dbi->genericSqlQuery("UPDATE mysql.user SET lock_tables_priv='Y'"
533                                               ." WHERE mysql.user='$username'");
534                         $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
535                         echo "mysql.user user='$username'", _("fixed"), "<br />\n";
536                     } elseif (!$row) {
537                         echo " <b><font color=\"red\">", _("FAILED"), "</font></b>: ",
538                             "Neither mysql.db nor mysql.user has a user='$username'"
539                             ." or the lock_tables_priv field",
540                             "<br />\n";
541                     } else {
542                         echo _("OK"), "<br />\n";
543                     }
544                 } else {
545                     echo _("OK"), "<br />\n";
546                 }
547                 //mysql_select_db($this->dbi->_backend->database(), $this->dbi->_backend->connection());
548             } else {
549                 echo sprintf(_("version <em>%s</em> not affected"), $mysql_version),"<br />\n";
550             }
551         }
552
553         // 1.3.10 mysql requires page.id auto_increment
554         // mysql, mysqli or mysqlt
555         if ($this->phpwiki_version >= 1030.099 and substr($backend_type,0,5) == 'mysql' 
556             and DATABASE_TYPE != 'PDO') 
557         {
558             echo _("check for mysql page.id auto_increment flag")," ...";
559             assert(!empty($page_tbl));
560             $database = $this->dbi->_backend->database();
561             $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
562             $columns = mysql_num_fields($fields); 
563             for ($i = 0; $i < $columns; $i++) {
564                 if (mysql_field_name($fields, $i) == 'id') {
565                     $flags = mysql_field_flags($fields, $i);
566                     //DONE: something was wrong with ADODB here.
567                     if (!strstr(strtolower($flags), "auto_increment")) {
568                         echo "<b>",_("ADDING"),"</b>"," ... ";
569                         // MODIFY col_def valid since mysql 3.22.16,
570                         // older mysql's need CHANGE old_col col_def
571                         $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE id"
572                                                     ." id INT NOT NULL AUTO_INCREMENT");
573                         $fields = mysql_list_fields($database, $page_tbl);
574                         if (!strstr(strtolower(mysql_field_flags($fields, $i)), "auto_increment"))
575                             echo " <b><font color=\"red\">", _("FAILED"), "</font></b><br />\n";
576                         else     
577                             echo _("OK"), "<br />\n";
578                     } else {
579                         echo _("OK"), "<br />\n";
580                     }
581                     break;
582                 }
583             }
584             mysql_free_result($fields);
585         }
586
587         // Check for mysql 4.1.x/5.0.0a binary search problem.
588         //   http://bugs.mysql.com/bug.php?id=4398
589         // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
590         // Confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
591         // On windows only, though utf8 would be useful elsewhere also.
592         // Illegal mix of collations (latin1_bin,IMPLICIT) and 
593         // (utf8_general_ci, COERCIBLE) for operation '='])
594         if (isWindows() and substr($backend_type,0,5) == 'mysql') {
595             echo _("check for mysql 4.1.x/5.0.0 binary search on windows problem")," ...";
596             $mysql_version = $this->dbi->_backend->_serverinfo['version'];
597             if ($mysql_version < 401.0) { 
598                 echo sprintf(_("version <em>%s</em>"), $mysql_version)," ",
599                     _("not affected"),"<br />\n";
600             } elseif ($mysql_version >= 401.6) { // FIXME: since which version?
601                 $row = $this->dbi->_backend->getRow("SHOW CREATE TABLE $page_tbl");
602                 $result = join(" ", $row);
603                 if (strstr(strtolower($result), "character set") 
604                     and strstr(strtolower($result), "collate")) 
605                     {
606                         echo _("OK"), "<br />\n";
607                     } else {
608                     //SET CHARACTER SET latin1
609                     $charset = CHARSET;
610                     if ($charset == 'iso-8859-1') $charset = 'latin1';
611                     $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename "
612                                           ."pagename VARCHAR(100) "
613                                           ."CHARACTER SET '$charset' COLLATE '$charset"."_bin' NOT NULL");
614                     echo sprintf(_("version <em>%s</em>"), $mysql_version), 
615                         " <b>",_("FIXED"),"</b>",
616                         "<br />\n";
617                 }
618             } elseif (DATABASE_TYPE != 'PDO') {
619                 // check if already fixed
620                 extract($this->dbi->_backend->_table_names);
621                 assert(!empty($page_tbl));
622                 $database = $this->dbi->_backend->database();
623                 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
624                 $columns = mysql_num_fields($fields); 
625                 for ($i = 0; $i < $columns; $i++) {
626                     if (mysql_field_name($fields, $i) == 'pagename') {
627                         $flags = mysql_field_flags($fields, $i);
628                         // I think it was fixed with 4.1.6, but I tested it only with 4.1.8
629                         if ($mysql_version > 401.0 and $mysql_version < 401.6) {
630                             // remove the binary flag
631                             if (strstr(strtolower($flags), "binary")) {
632                                 // FIXME: on duplicate pagenames this will fail!
633                                 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename"
634                                                       ." pagename VARCHAR(100) NOT NULL");
635                                 echo sprintf(_("version <em>%s</em>"), $mysql_version), 
636                                     "<b>",_("FIXED"),"</b>"
637                                     ,"<br />\n";        
638                             }
639                         }
640                         break;
641                     }
642                 }
643             }
644         }
645         if ($this->isSQL and ACCESS_LOG_SQL & 2) {
646             echo _("check for ACCESS_LOG_SQL passwords in POST requests")," ...";
647             // Don't display passwords in POST requests (up to 2005-02-04 12:03:20)
648             $res = $this->dbi->genericSqlIter("SELECT time_stamp, remote_host, " .
649                                         "request_args FROM ${prefix}accesslog WHERE request_args LIKE " .
650                                         "'%s:6:\"passwd\"%' AND request_args NOT LIKE '%s:6:\"passwd\";" .
651                                         "s:15:\"<not displayed>\"%'");
652             $count = 0;
653             while ($row = $res->next()) {
654                 $args = preg_replace("/(s:6:\"passwd\";s:15:\").*(\")/", 
655                                      "$1<not displayed>$2", $row["request_args"]);
656                 $ts = $row["time_stamp"];
657                 $rh = $row["remote_host"];
658                 $this->dbi->genericSqlQuery("UPDATE ${prefix}accesslog SET " .
659                                       "request_args='$args' WHERE time_stamp=$ts AND " .
660                                       "remote_host='$rh'");
661                 $count++;
662             }
663             if ($count > 0)
664                 echo "<b>",_("FIXED"),"</b>", "<br />\n";
665             else 
666                 echo _("OK"),"<br />\n";
667
668             if ($this->phpwiki_version >= 1030.13) {
669                 echo _("check for ACCESS_LOG_SQL remote_host varchar(50)")," ...";
670                 $database = $this->dbi->_backend->database();
671                 $accesslog_tbl = $prefix . 'accesslog';
672                 $fields = $this->dbi->_backend->listOfFields($database, $accesslog_tbl);
673                 if (!$fields) {
674                     echo _("SKIP");
675                 } elseif (strstr(strtolower(join(':', $sess_fields)), "remote_host")) {
676                     // TODO: how to check size, already done?
677                     echo "<b>",_("FIXING"),"remote_host</b>"," ... ";
678                     $this->dbi->genericSqlQuery("ALTER TABLE $accesslog_tbl CHANGE remote_host VARCHAR(100)");
679                 } else {
680                     echo _("FAIL");
681                 }
682                 echo "<br />\n";
683             }
684         }
685         $this->_upgrade_cached_html();
686
687         if ($this->db_version < $this->current_db_version) {
688             $this->dbi->set_db_version($this->current_db_version);
689             $this->db_version = $this->dbi->get_db_version();
690             echo "db version: upgrade to ", $this->db_version,"  ";
691             echo _("OK"), "<br />\n";
692             flush();
693         }
694
695         return;
696     }
697
698     /**
699      * Filter SQL missing permissions errors.
700      *
701      * A wrong DBADMIN user will not be able to connect
702      * @see _is_false_error, ErrorManager
703      * @access private
704      */
705     function _dbpermission_filter($err) {
706         if  ( $err->isWarning() ) {
707             global $ErrorManager;
708             $this->error_caught = 1;
709             $ErrorManager->_postponed_errors[] = $err;
710             return true;
711         }
712         return false;
713     }
714
715     function _try_dbadmin_user ($user, $passwd) {
716         global $DBParams, $DBAuthParams;
717         $AdminParams = $DBParams;
718         if (DATABASE_TYPE == 'SQL')
719             $dsn = DB::parseDSN($AdminParams['dsn']);
720         else {
721             $dsn = parseDSN($AdminParams['dsn']);
722         }
723         $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
724                                       $dsn['phptype'],
725                                       $user,
726                                       $passwd,
727                                       $dsn['hostspec'],
728                                       $dsn['database']);
729         $AdminParams['_tryroot_from_upgrade'] = 1;
730         // add error handler to warn about missing permissions for DBADMIN_USER
731         global $ErrorManager;
732         $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_dbpermission_filter'));
733         $this->error_caught = 0;
734         $this->dbi = WikiDB::open($AdminParams);
735         if (!$this->error_caught) return true; 
736         // FAILED: redo our connection with the wikiuser
737         $this->dbi = WikiDB::open($DBParams);
738         $ErrorManager->flushPostponedErrors();
739         $ErrorManager->popErrorHandler();
740         return false;
741     }
742
743     function _db_init () {
744         if (!$this->isSQL) return;
745
746         /* SQLite never needs admin params */
747         $backend_type = $this->dbi->_backend->backendType();
748         if (substr($backend_type,0,6)=="sqlite") {
749             return;
750         }
751         $dbadmin_user = 'root';
752         if ($dbadmin = $this->request->getArg('dbadmin')) {
753             $dbadmin_user = $dbadmin['user'];
754             if (isset($dbadmin['cancel'])) {
755                 return;
756             } elseif (!empty($dbadmin_user)) {
757                 if ($this->_try_dbadmin_user($dbadmin['user'], $dbadmin['passwd']))
758                     return;
759             }
760         } elseif (DBADMIN_USER) {
761             if ($this->_try_dbadmin_user(DBADMIN_USER, DBADMIN_PASSWD))
762                 return true;
763         }
764         // Check if the privileges are enough. Need CREATE and ALTER perms. 
765         // And on windows: SELECT FROM mysql, possibly: UPDATE mysql.
766         $form = HTML::form(array("method" => "post", 
767                                  "action" => $this->request->getPostURL(),
768                                  "accept-charset"=>$GLOBALS['charset']),
769                            HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."),
770                                    HTML::br(),
771                                    _("And on windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")),
772                            HiddenInputs(array('action' => 'upgrade',
773                                               'overwrite' => $this->request->getArg('overwrite'))),
774                            HTML::table(array("cellspacing"=>4),
775                                        HTML::tr(HTML::td(array('align'=>'right'),
776                                                          _("DB admin user:")),
777                                                 HTML::td(HTML::input(array('name'=>"dbadmin[user]",
778                                                                            'size'=>12,
779                                                                            'maxlength'=>256,
780                                                                            'value'=>$dbadmin_user)))),
781                                        HTML::tr(HTML::td(array('align'=>'right'),
782                                                          _("DB admin password:")),
783                                                 HTML::td(HTML::input(array('name'=>"dbadmin[passwd]",
784                                                                            'type'=>'password',
785                                                                            'size'=>12,
786                                                                            'maxlength'=>256)))),
787                                        HTML::tr(HTML::td(array('align'=>'center', 'colspan' => 2),
788                                                          Button("submit:", _("Submit"), 'wikiaction'), 
789                                                          HTML::raw('&nbsp;'),
790                                                          Button("submit:dbadmin[cancel]", _("Cancel"), 
791                                                                 'button')))));
792         $form->printXml();
793         echo "</div><!-- content -->\n";
794         echo asXML(Template("bottom"));
795         echo "</body></html>\n";
796         $this->request->finish();
797         exit();
798     }
799
800     /**
801      * if page.cached_html does not exists:
802      *   put _cached_html from pagedata into a new seperate blob, 
803      *   not into the huge serialized string.
804      *
805      * It is only rarelely needed: for current page only, if-not-modified,
806      * but was extracetd for every simple page iteration.
807      */
808     function _upgrade_cached_html ( $verbose=true ) {
809         global $DBParams;
810         if (!$this->isSQL) return;
811         $count = 0;
812         if ($this->phpwiki_version >= 1030.10) {
813             if ($verbose)
814                 echo _("check for extra page.cached_html column")," ... ";
815             $database = $this->dbi->_backend->database();
816             extract($this->dbi->_backend->_table_names);
817             $fields = $this->dbi->_backend->listOfFields($database, $page_tbl);
818             if (!$fields) {
819                 echo _("SKIP"), "<br />\n";
820                 return 0;
821             }
822             if (!strstr(strtolower(join(':', $fields)), "cached_html")) {
823                 if ($verbose)
824                     echo "<b>",_("ADDING"),"</b>"," ... ";
825                 $backend_type = $this->dbi->_backend->backendType();
826                 if (substr($backend_type,0,5) == 'mysql')
827                     $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB");
828                 else
829                     $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB");
830                 if ($verbose)
831                     echo "<b>",_("CONVERTING"),"</b>"," ... ";
832                 $count = _convert_cached_html();
833                 if ($verbose)
834                     echo $count, " ", _("OK"), "<br />\n";
835             } else {
836                 if ($verbose)
837                     echo _("OK"), "<br />\n";
838             }
839         }
840         return $count;
841     }
842
843     /** 
844      * move _cached_html for all pages from pagedata into a new seperate blob.
845      * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also.
846      */
847     function _convert_cached_html () {
848         global $DBParams;
849         if (!$this->isSQL) return;
850         //if (!in_array(DATABASE_TYPE, array('SQL','ADODB'))) return;
851
852         $pages = $this->dbi->getAllPages();
853         $cache =& $this->dbi->_cache;
854         $count = 0;
855         extract($this->dbi->_backend->_table_names);
856         while ($page = $pages->next()) {
857             $pagename = $page->getName();
858             $data = $this->dbi->_backend->get_pagedata($pagename);
859             if (!empty($data['_cached_html'])) {
860                 $cached_html = $data['_cached_html'];
861                 $data['_cached_html'] = '';
862                 $cache->update_pagedata($pagename, $data);
863                 // store as blob, not serialized
864                 $this->dbi->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?",
865                                       array($cached_html, $pagename));
866                 $count++;
867             }
868         }
869         return $count;
870     }
871
872     /**
873      * upgrade to 1.3.13 link structure.
874      */
875     function _upgrade_relation_links ( $verbose=true ) {
876         if ($this->phpwiki_version >= 1030.12200610 and $this->isSQL) {
877             echo _("check for relation field in link table")," ...";
878             $database = $this->dbi->_backend->database();
879             $link_tbl = $prefix . 'link';
880             $fields = $this->dbi->_backend->listOfFields($database, $link_tbl);
881             if (!$fields) {
882                 echo _("SKIP");
883             } elseif (strstr(strtolower(join(':', $fields)), "link")) {
884                 echo "<b>",_("ADDING")," relation</b>"," ... ";
885                 $this->dbi->genericSqlQuery("ALTER TABLE $link_tbl ADD relation INT DEFAULT 0;");
886                 $this->dbi->genericSqlQuery("CREATE INDEX link_relation ON $link_tbl (relation);");
887             } else {
888                 echo _("FAIL");
889             }
890             echo "<br />\n";
891         }
892         if ($this->phpwiki_version >= 1030.12200610) {
893             echo _("Rebuild entire database to upgrade relation links")," ... ";
894             if (DATABASE_TYPE == 'dba') {
895                 echo "<b>",_("CONVERTING")," dba linktable</b>","(~2 min, max 4 min) ... ";
896                 flush();
897                 longer_timeout(240);
898                 $this->dbi->_backend->_linkdb->rebuild();
899             } else {
900                 flush();
901                 longer_timeout(180);
902                 $this->dbi->_backend->rebuild();
903             }
904             echo _("OK"), "<br />\n";
905         }
906     }
907
908     function CheckPluginUpdate() {
909         return;
910         
911         echo "<h3>",sprintf(_("check for necessary %s updates"),
912                             _("plugin argument")),"</h3>\n";
913                             
914         $this->_configUpdates = array();
915         $this->_configUpdates[] = new UpgradePluginEntry
916             ($this, array('key' => 'plugin_randompage_numpages', 
917                           'fixed_with' => 1012.0,
918                           //'header' => _("change RandomPage pages => numpages"),
919                           //'notice'  =>_("found RandomPage plugin"),
920                           'check_args' => array("plugin RandomPage pages",
921                                                 "/(<\?\s*plugin\s+ RandomPage\s+)pages/",
922                                                 "\\1numpages")));
923         $this->_configUpdates[] = new UpgradePluginEntry
924             ($this, array('key' => 'plugin_createtoc_position', 
925                           'fixed_with' => 1013.0,
926                           //'header' => _("change CreateToc align => position"),
927                           //'notice'  =>_("found CreateToc plugin"),
928                           'check_args' => array("plugin CreateToc align",
929                                                 "/(<\?\s*plugin\s+ CreateToc[^\?]+)align/",
930                                                 "\\1position")));
931
932         if (empty($this->_configUpdates)) return;
933         foreach ($this->_configUpdates as $update) {
934             $pages = $this->dbi->fullSearch($this->check_args[0]);
935             while ($page = $allpages->next()) {
936                 $current = $page->getCurrentRevision();
937                 $pagetext = $current->getPackedContent();
938                 $update->check($this->check_args[1], $this->check_args[2], $pagetext, $page, $current);
939             }
940         }
941         free($allpages);
942         unset($pagetext);
943         unset($current);
944         unset($page);
945     }
946
947     /**
948      * preg_replace over local file.
949      * Only line-orientated matches possible.
950      */
951     function fixLocalFile($match, $replace, $filename) {
952         $o_filename = $filename;
953         if (!file_exists($filename))
954             $filename = FindFile($filename);
955         if (!file_exists($filename))
956             return array(false, sprintf(_("file %s not found"), $o_filename));
957         $found = false;
958         if (is_writable($filename)) {
959             $in = fopen($filename, "rb");
960             $out = fopen($tmp = tempnam(getUploadFilePath(),"cfg"), "wb");
961             if (isWindows())
962                 $tmp = str_replace("/","\\",$tmp);
963             // Detect the existing linesep at first line. fgets strips it even if 'rb'.
964             // Before we simply assumed \r\n on windows local files.
965             $s = fread($in, 1024);
966             rewind($in);
967             $linesep = (substr_count($s, "\r\n") > substr_count($s, "\n")) ? "\r\n" : "\n";
968             //$linesep = isWindows() ? "\r\n" : "\n";
969             while ($s = fgets($in)) {
970                 // =>php-5.0.1 can fill count
971                 //$new = preg_replace($match, $replace, $s, -1, $count);
972                 $new = preg_replace($match, $replace, $s);
973                 if ($new != $s) {
974                     $s = $new . $linesep;
975                     $found = true;
976                 }
977                 fputs($out, $s);
978             }
979             fclose($in);
980             fclose($out);
981             if (!$found) {
982                 // todo: skip
983                 $reason = sprintf(_("%s not found in %s"), $match, $filename);
984                 unlink($out);
985                 return array($found, $reason);
986             } else {
987                 @unlink("$file.bak");
988                 @rename($file,"$file.bak");
989                 if (!rename($tmp, $file))
990                     return array(false, sprintf(_("couldn't move %s to %s"), $tmp, $filename));
991                 return true;    
992             }
993         } else {
994             return array(false, sprintf(_("file %s is not writable"), $filename));
995         }
996     }
997
998     function CheckConfigUpdate () {
999         echo "<h3>",sprintf(_("check for necessary %s updates"),
1000                             "config.ini"),"</h3>\n";
1001         $entry = new UpgradeConfigEntry
1002             ($this, array('key' => 'cache_control_none', 
1003                           'fixed_with' => 1012.0,
1004                           'header' => sprintf(_("check for %s"),"CACHE_CONTROL = NONE"),
1005                           'applicable_args' => 'CACHE_CONTROL',
1006                           'notice'  => _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"),
1007                           'check_args' => array("/^\s*CACHE_CONTROL\s*=\s*NONE/", "CACHE_CONTROL = NO_CACHE")));
1008         $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1009         $this->_configUpdates[] = $entry;
1010         
1011         $entry = new UpgradeConfigEntry
1012             ($this, array('key' => 'group_method_none', 
1013                           'fixed_with' => 1012.0,
1014                           'header' => sprintf(_("check for %s"), "GROUP_METHOD = NONE"),
1015                           'applicable_args' => 'GROUP_METHOD',
1016                           'notice'  =>_("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""),
1017                           'check_args' => array("/^\s*GROUP_METHOD\s*=\s*NONE/", "GROUP_METHOD = \"NONE\"")));
1018         $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1019         $this->_configUpdates[] = $entry;
1020
1021         $entry = new UpgradeConfigEntry
1022             ($this, array('key' => 'blog_empty_default_prefix', 
1023                           'fixed_with' => 1013.0,
1024                           'header' => sprintf(_("check for %s"), "BLOG_EMPTY_DEFAULT_PREFIX"),
1025                           'applicable_args' => 'BLOG_EMPTY_DEFAULT_PREFIX',
1026                           'notice'  =>_("fix BLOG_EMPTY_DEFAULT_PREFIX into BLOG_DEFAULT_EMPTY_PREFIX"),
1027                           'check_args' => array("/BLOG_EMPTY_DEFAULT_PREFIX\s*=/","BLOG_DEFAULT_EMPTY_PREFIX =")));
1028         $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined'));
1029         $this->_configUpdates[] = $entry;
1030
1031         // TODO: find extra file updates
1032         if (empty($this->_configUpdates)) return;
1033         foreach ($this->_configUpdates as $update) {
1034             $update->check();
1035         }
1036     }
1037
1038 } // class Upgrade
1039
1040 class UpgradeEntry 
1041 {
1042     /**
1043      * Add an upgrade item to be checked.
1044      *
1045      * @param $parent object The parent Upgrade class to inherit the version properties
1046      * @param $key string    A short unique key to store success in the WikiDB
1047      * @param $fixed_with double @see phpwiki_version() number
1048      * @param $header string Optional header to be printed always even if not applicable
1049      * @param $applicable WikiCallback Optional callback boolean applicable()
1050      * @param $notice string Description of the check
1051      * @param $method WikiCallback Optional callback array method(array)
1052      * //param All other args are passed to $method
1053      */
1054     function UpgradeEntry(&$parent, $params) {
1055         $this->parent =& $parent;           // get the properties db_version
1056         foreach (array('key' => 'required',
1057                         // the wikidb stores the version when we actually fixed that.
1058                        'fixed_with' => 'required',
1059                        'header' => '',          // always printed
1060                        'applicable_cb' => null, // method to check if applicable 
1061                        'applicable_args' => array(), // might be the config name
1062                        'notice' => '', 
1063                        'check_cb' => null,      // method to apply
1064                        'check_args' => array()) 
1065                  as $k => $v)
1066         {
1067             if (!isset($params[$k])) { // default
1068                 if ($v == 'required') trigger_error("Required arg $k missing", E_USER_ERROR);
1069                 else $this->{$k} = $v;
1070             } else { 
1071                 $this->{$k} = $params[$k];
1072             }
1073         }
1074         if (!is_array($this->applicable_args)) // single arg convenience shortcut
1075             $this->applicable_args = array($this->applicable_args);
1076         if (!is_array($this->check_args))   // single arg convenience shortcut
1077             $this->check_args = array($this->check_args);
1078         if ($this->notice === '' and count($this->applicable_args) > 0)
1079             $this->notice = 'Check for '.join(', ', $this->applicable_args);
1080         $this->_db_key = "_upgrade";
1081         $this->upgrade = $this->parent->dbi->get($this->_db_key);
1082     }
1083     /* needed ? */
1084     function setApplicableCb($object) {
1085         $this->applicable_cb =& $object; 
1086     }
1087     function _check_if_already_fixed() {
1088         // not yet fixed?
1089         if (!isset($this->upgrade['name'])) return false;
1090         // override with force?
1091         if ($this->parent->request->getArg('force')) return false;
1092         // already fixed and with an ok version
1093         if ($this->upgrade['name'] >= $this->fixed_with) return $this->upgrade['name'];
1094         // already fixed but with an older version. do it again.
1095         return false;
1096     }
1097     function pass() {
1098         // store in db no to fix again
1099         $this->upgrade['name'] = $this->parent->phpwiki_version;
1100         $this->parent->dbi->set($this->_db_key, $this->upgrade);
1101         echo "<b>",_("FIXED"),"</b>";
1102         if (isset($this->reason))
1103             echo ": ", $this->reason;
1104         echo "<br />\n";
1105         flush();
1106         return true;
1107     }
1108     function fail() {
1109         echo " <b><font color=\"red\">", _("FAILED"), "</font></b>";
1110         if (isset($this->reason))
1111             echo ": ", $this->reason;
1112         echo "<br />\n";
1113         flush();
1114         return false;
1115     }
1116     function skip() { // not applicable
1117         if (isset($this->silent_skip)) return true;
1118         echo _(" skipped"),".<br />\n";
1119         flush();
1120         return true;
1121     }
1122     function check($args = null) {
1123         if ($this->header) echo $this->header, ' ... ';
1124         if ($when = $this->_check_if_already_fixed()) {
1125             // be totally silent if no header is defined.
1126             if ($this->header) echo _("fixed with")," ",$when,"<br />\n";
1127             flush();
1128             return true;
1129         }
1130         if (is_object($this->applicable_cb)) {
1131             if (!$this->applicable_cb->call_array($this->applicable_args))
1132                 return $this->skip();
1133         }
1134         if ($this->notice) {
1135             if ($this->header)
1136                 echo "<br />\n";
1137             echo $this->notice," ";
1138             flush();
1139         }
1140         if (!is_null($args)) $this->check_args =& $args;
1141         if (is_object($this->check_cb))
1142             $do = $this->method_cb->call_array($this->check_args);
1143         else
1144             $do = $this->default_method($this->check_args);
1145         if (is_array($do)) {
1146             $this->reason = $do[1];
1147             $do = $do[0];
1148         }
1149         return $do ? $this->pass() : $this->fail();
1150     }
1151 } // class UpgradeEntry
1152
1153 class UpgradeConfigEntry extends UpgradeEntry {
1154     function _applicable_defined() {
1155         return (boolean)defined($this->applicable_args[0]);
1156     }
1157     function _applicable_defined_and_empty() {
1158         $const = $this->applicable_args[0];
1159         return (boolean)(defined($const) and !constant($const));
1160     }
1161     function default_method ($args) {
1162         $match = $args[0];
1163         $replace = $args[1];
1164         return $this->parent->fixLocalFile($match, $replace, "config/config.ini");
1165     }
1166 } // class UpdateConfigEntry
1167
1168 /* This is different */
1169 class UpgradePluginEntry extends UpgradeEntry {
1170     
1171    /**
1172      * check all pages for a plugin match
1173      */
1174     var $silent_skip = 1;
1175      
1176     function default_method (&$args) {
1177         $match    =  $args[0];
1178         $replace  =  $args[1];
1179         $pagetext =& $args[2];
1180         $page     =& $args[3];
1181         $current  =& $args[4];
1182         if (preg_match($match, $pagetext)) {
1183             echo $page->getName()," ",$this->notice," ... ";
1184             if ($newtext = preg_replace($match, $replace, $pagetext)) {
1185                 $meta = $current->_data;
1186                 $meta['summary'] = "upgrade: ".$this->header;
1187                 $page->save($newtext, $current->getVersion() + 1, $meta);
1188                 $this->pass();
1189             } else {
1190                 $this->fail();
1191             }
1192         }
1193     }
1194 } // class UpdatePluginEntry
1195
1196 /**
1197  * fix custom themes which are not in our distribution
1198  * this should be optional
1199  */
1200 class UpgradeThemeEntry extends UpgradeEntry {
1201
1202     function default_method (&$args) {
1203         $match    =  $args[0];
1204         $replace  =  $args[1];
1205         $template = $args[2];
1206     }
1207     
1208     function fixThemeTemplate($match, $new, $template) {
1209         // for all custom themes
1210         $ourthemes = explode(":","blog:Crao:default:Hawaiian:MacOSX:MonoBook:Portland:shamino_com:SpaceWiki:wikilens:Wordpress");
1211         $themedir = NormalizeLocalFileName("themes");
1212         $dh = opendir($themedir);
1213         while ($r = readdir($dh)) {
1214             if (filetype($r) == 'dir' and $r[0] != '.' and !is_array($r, $ourthemes))
1215                 $customthemes[] = $r;
1216         }
1217         $success = true;
1218         $errors = '';
1219         foreach ($customthemes as $customtheme) {
1220             $template = FindFile("themes/$customtheme/templates/$template");
1221             $do = $this->parent->fixLocalFile($match, $new, template);
1222             if (!$do[0]) {
1223                 $success = false;
1224                 $errors .= $do[1]." "; 
1225                 echo $do[1];
1226             }
1227         }
1228         return array($success, $errors);
1229     }
1230 }
1231
1232 /**
1233  * TODO:
1234  *
1235  * Upgrade: Base class for multipage worksteps
1236  * identify, validate, display options, next step
1237  */
1238 /*
1239 */
1240
1241 // TODO: At which step are we? 
1242 // validate and do it again or go on with next step.
1243
1244 /** entry function from lib/main.php
1245  */
1246 function DoUpgrade(&$request) {
1247
1248     if (!$request->_user->isAdmin()) {
1249         $request->_notAuthorized(WIKIAUTH_ADMIN);
1250         $request->finish(
1251                          HTML::div(array('class' => 'disabled-plugin'),
1252                                    fmt("Upgrade disabled: user != isAdmin")));
1253         return;
1254     }
1255     // TODO: StartLoadDump should turn on implicit_flush.   
1256     @ini_set("implicit_flush", true);
1257     StartLoadDump($request, _("Upgrading this PhpWiki"));
1258     $upgrade = new Upgrade($request);
1259     //if (!$request->getArg('noindex'))
1260     //    CheckOldIndexUpdate($request); // index.php => config.ini to upgrade from < 1.3.10
1261     if (!$request->getArg('nodb'))
1262         $upgrade->CheckDatabaseUpdate($request);   // first check cached_html and friends
1263     if (!$request->getArg('nopgsrc')) {
1264         $upgrade->CheckActionPageUpdate($request);
1265         $upgrade->CheckPgsrcUpdate($request);
1266     }
1267     if (!$request->getArg('noplugin'))
1268         $upgrade->CheckPluginUpdate($request);
1269     if (!$request->getArg('noconfig'))
1270         $upgrade->CheckConfigUpdate($request);
1271     // This is optional and should be linked. In EndLoadDump or PhpWikiAdministration?
1272     //if ($request->getArg('theme'))
1273     //    $upgrade->CheckThemeUpdate($request);
1274     EndLoadDump($request);
1275 }
1276
1277 // For emacs users
1278 // Local Variables:
1279 // mode: php
1280 // tab-width: 8
1281 // c-basic-offset: 4
1282 // c-hanging-comment-ender-p: nil
1283 // indent-tabs-mode: nil
1284 // End:
1285 ?>