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