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