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