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