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