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