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