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